🐕

List<T>を1行でDBにバルクインサートする

2024/03/30に公開

はじめに

大量のデータをDBに保存する手段の1つにバルクインサートというものがあります。
具体的に言うと以下のようなSQLのことです。

insert into hoge
values
(1,'text')
, (2,'abc')

1つのSQLでN行をDBに送ることで、高速に登録処理を行うことができます。

本記事は、このバルクインサートクエリを1行で作成する手法について紹介します。

前提条件

C#を使用します。
DBMSは不問です。
ORMは使用しません。
SQLビルダ(Carbunql)を使用します。
SQL実行にDapper、Carbunql.Dapperを使用します。
DBに保存したい内容はList<T>でインスタンスされているものとします。

ライブラリのインストール

PM> NuGet\Install-Package Carbunql
PM> NuGet\Install-Package Carbunql.Dapper

https://github.com/mk3008/Carbunql
https://github.com/DapperLib/Dapper

テストに使用するクラス

DBに保存するモデルを以下のように定義するとします。

public class ApiResult
{
	public int ResultId { get; set; }
	public string? ResultText { get; set; }
}

テストに使用するインスタンス

上記で定義したモデルをList<T>の形式でインスタンスします。

var results = new List<ApiResult>()
{
    new (){ ResultId = 1 , ResultText = "a"},
    new (){ ResultId = 2 , ResultText = "b"},
    new (){ ResultId = 3 , ResultText = null},
};	

バルクインサートクエリを作ろう!

var sql = results.ToSelectQuery().ToInsertQuery("table").ToCommand().CommandText;

That's all!

生成されるSQL

このようになります。
パラメータクエリになっていることに注目ください。

INSERT INTO
    table (
        result_id, result_text
    )
SELECT
    v.result_id,
    v.result_text
FROM
    (
        VALUES
            (:r0c0, :r0c1),
            (:r1c0, :r1c1),
            (:r2c0, :r2c1)
    ) AS v (
        result_id, result_text
    )

なお、パラメータは以下のコードで取得できます。

results.ToSelectQuery().ToInsertQuery("table").ToCommand().Parameters

ただ、実行するだけならクエリ文字列、パラメータを取得する必要はありません。
Carbunql.Dapperを使って以下のように記述するだけです。

IDbConnection cn = CreateOpenConnection(); //TODO : DBコネクションを取得する関数
cn.Execute(results.ToSelectQuery().ToInsertQuery("table"));

SQLを環境に合わせてカスタムしたい

生成されるSQLはデフォルトでは以下のようになっています。

  • パラメータ識別子が「:」になっている。(Postgres用)
  • 列名はプロパティ名がスネークケースになっている。

環境によってはこのような加工は望ましくない場合があります。この場合は少し手を加えてください。

ToSelectQuery関数の定義

定義はこの様になっています。

public static SelectQuery ToSelectQuery<T>(
    this IEnumerable<T> source,
    string placeholderIndentifer,
    Func<string, string> propertyNameConverter
)

第1引数:string placeholderIndentifer

パラメータ識別子です。
SQLServerなら"@"を指定すればOK。

第2引数:Func<string, string> propertyNameConverter

プロパティ名を列名に変換する関数。
デフォルトはスネークケースに変換する関数がセットされるため、必要に応じて任意の関数をセットしてください。

発展:テンポラリテーブルを作成したい

List<T>のため、テーブル構造と一致しているとは限りません。
この場合、テンポラリテーブルを作ってDBサーバー上で加工する、という手法を取ることがあります。
このようなケースでは以下のようにしてください。

var sql = results.ToSelectQuery().ToCreateTableQuery("table", isTemporary: true).ToCommand().CommandText;

こちらもワンライナーで書くことができます。

まとめ

Carbunqlを使用すると、SQLを簡単に生成、加工できます。
ぜひご利用ください。

補足

本記事で紹介したソースコードの全文はこちらにあります。ご参考ください。
(本記事のコードと全く同じではないですが、ニュアンスはほぼ同じです)
https://github.com/mk3008/Carbunql/blob/main/test/Carbunql.Building.Test/IEnumerableTest.cs

Discussion