高パフォーマンスのテーブル パラメーター メソッド ( http://www.altdevblogaday.com/2012/05/16/sql-server-high-performance-inserts/ )を使用してレコードを挿入しようとしていますが、それが挿入した各レコードの ID 値を取得できます。
現時点では、答えはノーのようです。データを挿入してから ID 値を取得しましたが、一致しません。具体的には、約 75% の確率で一致せず、予測できない方法で一致することもありません。この問題を再現するコードを次に示します。
// Create a datatable with 100k rows
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("item_id", typeof(int)));
dt.Columns.Add(new DataColumn("comment", typeof(string)));
for (int i = 0; i < 100000; i++) {
dt.Rows.Add(new object[] { 0, i.ToString() });
}
// Insert these records and retrieve back the identity
using (SqlConnection conn = new SqlConnection("Data Source=localhost;Initial Catalog=testdb;Integrated Security=True")) {
conn.Open();
using (SqlCommand cmd = new SqlCommand("proc_bulk_insert_test", conn)) {
cmd.CommandType = CommandType.StoredProcedure;
// Adding a "structured" parameter allows you to insert tons of data with low overhead
SqlParameter param = new SqlParameter("@mytable", SqlDbType.Structured);
param.Value = dt;
cmd.Parameters.Add(param);
SqlDataReader dr = cmd.ExecuteReader();
// Set all the records' identity values
int i = 0;
while (dr.Read()) {
dt.Rows[i].ItemArray = new object[] { dr.GetInt32(0), dt.Rows[i].ItemArray[1] };
i++;
}
dr.Close();
}
// Do all the records' ID numbers match what I received back from the database?
using (SqlCommand cmd = new SqlCommand("SELECT * FROM bulk_insert_test WHERE item_id >= @base_identity ORDER BY item_id ASC", conn)) {
cmd.Parameters.AddWithValue("@base_identity", (int)dt.Rows[0].ItemArray[0]);
SqlDataReader dr = cmd.ExecuteReader();
DataTable dtresult = new DataTable();
dtresult.Load(dr);
}
}
データベースは、次の SQL サーバー スクリプトを使用して定義されます。
CREATE TABLE bulk_insert_test (
item_id int IDENTITY (1, 1) NOT NULL PRIMARY KEY,
comment varchar(20)
)
GO
CREATE TYPE bulk_insert_table_type AS TABLE ( item_id int, comment varchar(20) )
GO
CREATE PROCEDURE proc_bulk_insert_test
@mytable bulk_insert_table_type READONLY
AS
DECLARE @TableOfIdentities TABLE (IdentValue INT)
INSERT INTO bulk_insert_test (comment)
OUTPUT Inserted.item_id INTO @TableOfIdentities(IdentValue)
SELECT comment FROM @mytable
SELECT * FROM @TableOfIdentities
ここに問題があります: から返される値はproc_bulk_insert_test
、元のレコードが挿入された順序と同じではありません。したがって、ステートメントitem_id
から返された値を各レコードにプログラムで割り当てることはできません。OUTPUT
唯一の有効な解決策は、SELECT
挿入したレコードのリスト全体をバックアップすることのようですが、率直に言って、SQL Server のネットワーク カードを介してパイプされるデータの量を削減する解決策を希望します。ID値を取得しながら、大きな挿入に対するより良い解決策を持っている人はいますか?
編集:質問をもう少し明確にしてみましょう。問題は、挿入したばかりのデータに SQL Server が割り当てた ID 値を C# プログラムに学習させたいということです。順序は必須ではありません。しかし、C# 内で任意のレコード セットを取得し、高速テーブル パラメーター メソッドを使用してそれらを挿入し、テーブル全体をメモリに再クエリすることなく、C# で自動生成された ID 番号を割り当てることができるようにしたいと考えています。
これは人為的なテスト セットであるため、できるだけ小さな読み取り可能なコードに要約しようとしました。この問題を解決するために使用した方法を説明しましょう。
- 元のコードでは、この例の元となったアプリケーションで、1,500 万の個別の挿入ステートメントを使用して約 1,500 万行を挿入し、各挿入後に ID 値を取得していました。これは機能しましたが、遅かったです。
- 挿入用の高性能テーブル パラメータを使用してコードを修正しました。次に、C# のすべてのオブジェクトを破棄し、データベースからオブジェクト全体を読み戻します。ただし、元のレコードには多数の varchar 値と decimal 値を含む数十の列があったため、この方法は高速で機能していましたが、ネットワーク トラフィックが非常に集中していました。
- SQL Server に ID 値のみを報告するように依頼しながら、テーブル パラメーターの挿入を使用できるかどうかを調べるための調査を開始しました。私は試しましたが、これまでのところどちらでも成功していません
scope_identity()
。OUTPUT
基本的に、この問題は、SQL Server が常に指定した順序でレコードを挿入する場合に解決されます。テーブル値パラメーターの挿入で指定された順序で SQL サーバーにレコードを挿入させることは可能ですか?
EDIT2:このアプローチは、Cade Rouxが以下に引用しているものと非常に似ているようです:
ただし、この記事では、作成者は魔法の一意の値「ProductNumber」を使用して、挿入された情報を「出力」値から元のテーブル値パラメーターに接続します。テーブルに魔法のような一意の値がない場合、これを行う方法を見つけようとしています。