MySQL でも楽に bulk insert したい

以前 PostgreSQL で unnest を使って楽に bulk insert する方法を紹介した https://eagletmt.hateblo.jp/entry/2021/09/01/030453 。 これの MySQL 版が欲しかったものの array 型が存在しない MySQL では無理かなと思っていたんだけど、MySQL 8.0 からサポートされた JSON 型なら array を表現できて json_table() という関数を使うと達成できそうなことにふと気付いた。

json_table() を使うと JSON の値からテーブル (行) に変換することができる。つまりこれを使えば unnest に近いことが可能になる。
https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html

MySQL [(none)]> select * from json_table('[{"x": 1, "y": 2}, {"x": 3, "y": 4}]', '$[*]' columns (x integer path '$.x', y integer path '$.y')) as t;
+------+------+
| x    | y    |
+------+------+
|    1 |    2 |
|    3 |    4 |
+------+------+
2 rows in set (0.001 sec)

文法がなかなか独特だけど1つの JSON の値 (配列) から行に変換できている。なのでプログラム側は JSONシリアライズさえできれば insert into select で bulk insert が可能になる。 PostgreSQL 版と同様に Rust の sqlx を使った例はこんなかんじになる。

#[derive(Debug, serde::Serialize)]
struct Record {
    x: i32,
    y: i32,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let records: Vec<_> = (0..1000)
        .map(|i| Record {
            x: 2 * i,
            y: 2 * i + 1,
        })
        .collect();

    let pool = sqlx::MySqlPool::connect("mysql://...").await?;

    let query = "insert into bulk_inserts (x, y) select * from json_table(?, '$[*]' columns (x integer path '$.x', y integer path '$.y')) as t";
    sqlx::query(query)
        .bind(sqlx::types::Json(records))
        .execute(&pool)
        .await?;
    Ok(())
}

がんばってプレースホルダを組み立てる必要がなくなって便利。