SQLServerでDataTableの内容を一度のSQLでデータベースに更新する
DataTable の内容をデータベースに登録、または更新したい場合がある。
 しかし、DataTable の行をループしながら行毎に INSERT 文や UPDATE 文を発行すると IO 負荷が高くなり、パフォーマンスが悪化する。
 そこで、SQL Server のユーザー定義テーブル型を使用することにより一括での更新が可能となる。
前提条件:SQL Server 2008 以降
IO負荷が高い例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | Sub Test(ByVal dt As DataTable)     Dim con As New SqlConnection("connection string")     Dim cmd As New SqlCommand     Dim sql As New StringBuilder     con.Open()     cmd.Connection = con     cmd.Parameters.Clear()     'データテーブルの行分だけループしている悪い例     For i As Integer = 0 To dt.Rows.Count - 1         sql.AppendLine("INSERT INTO dbo.T_TEST ")         sql.AppendLine("( ")         sql.AppendLine("  CODE ")         sql.AppendLine(") VALUES ( ")         sql.AppendLine("  @Code ")         sql.AppendLine("); ")         'パラメータの作成         Dim param = New SqlParameter("@Code", SqlDbType.Int, ParameterDirection.Input)         param.Value = dt.Rows(i).Item(0).ToString()         'パラメータを渡します。         cmd.Parameters.Add(param)         'プロシージャの実行         cmd.CommandType = CommandType.Text         cmd.CommandText = sql.ToString()         cmd.ExecuteNonQuery     Next i End Sub | 
まずは、悪例としてデータテーブルのレコードを 1 件ずつ INSERT していく方法。
 上記の方法だと件数が少なければ問題が無いように感じるが、1万件を超えてくるとそのパフォーマンスの悪さが目立ってくる。
 1万件のデータをループして INSERT すると1万回の IO 処理となり、オーバーヘッドが大きくなる。
 しかし、次の方法で INSERT すると1回の IO 処理で済む為、更新するデータ量は変わらないがかなりのパフォーマンス改善が見込まれる。
DataTableをパラメータとして渡す
DataTable をパラメータとして渡す為には、SQL Server 側にユーザー定義テーブル型を作成しておく必要がある。
| 1 2 3 4 5 | CREATE TYPE TestTableType AS TABLE (   TestCode int NOT NULL ) GO | 
テーブルの定義と同様に括弧の中にフィールド定義を記述する。
 テーブルで定義できるフィールドはTableTypeでも定義可能。
続いて、プログラム側の処理。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | Sub Test(ByVal dt As DataTable)     Dim con As New SqlConnection("connection string")     Dim cmd As New SqlCommand     Dim sql As New StringBuilder     con.Open()     cmd.Connection = con     cmd.Parameters.Clear()     sql.AppendLine("INSERT INTO dbo.T_TEST ")     sql.AppendLine("      ( ")     sql.AppendLine("        CODE ")     sql.AppendLine("      ) ")     sql.AppendLine(" SELECT TestCode ")     sql.AppendLine("   FROM @TestTable ")     sql.AppendLine("  WHERE TestCode = @Code ")     sql.AppendLine("); ")     'パラメータの作成     Dim params(1) As SqlParameters     params(0) = New SqlParameter("@Code", SqlDbType.Int, ParameterDirection.Input)     params(0).Value = 1     params(1) = New SqlParameter("@TestTable", SqlDbType.Structured, ParameterDirection.Input)  'TypeにSqlDbType.Structuredを渡します。     params(1).TypeName = "TestTableType"  'テーブルタイプの名称を渡します。     params(1).Value = dt    '編集したデータテーブルを渡します。     'パラメータ配列を渡します。     cmd.Parameters.AddRange(params)     'プロシージャの実行     cmd.CommandType = CommandType.StoredProcedure     cmd.CommandText = sql.ToString()     cmd.ExecuteNonQuery End Sub | 
ユーザー定義テーブル型を使用することで DataTable をテーブルのように渡すことができる為、一括で SELECT して INSERT または UPDATE が可能となる。
<注意事項>
- DataTable のフィールドはユーザー定義テーブル型のフィールドと一致している必要がある。
- ユーザー定義テーブル型を渡す場合は、SqlDbType.Structured で渡す。
ユーザー定義テーブル型を作成しておく必要はあるが、こちらの方法であれば実行速度が圧倒的に速く、パフォーマンス改善が期待できる。
 デメリットとしては、ループせずに一括で更新する為、プログレスバーなどで進捗を表示することができない。
