2

高パフォーマンスのテーブル パラメーター メソッド ( 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. 元のコードでは、この例の元となったアプリケーションで、1,500 万の個別の挿入ステートメントを使用して約 1,500 万行を挿入し、各挿入後に ID 値を取得していました。これは機能しましたが、遅かったです。
  2. 挿入用の高性能テーブル パラメータを使用してコードを修正しました。次に、C# のすべてのオブジェクトを破棄し、データベースからオブジェクト全体を読み戻します。ただし、元のレコードには多数の varchar 値と decimal 値を含む数十の列があったため、この方法は高速で機能していましたが、ネットワーク トラフィックが非常に集中していました。
  3. SQL Server に ID 値のみを報告するように依頼しながら、テーブル パラメーターの挿入を使用できるかどうかを調べるための調査を開始しました。私は試しましたが、これまでのところどちらでも成功していませんscope_identity()OUTPUT

基本的に、この問題は、SQL Server が常に指定した順序でレコードを挿入する場合に解決されます。テーブル値パラメーターの挿入で指定された順序で SQL サーバーにレコードを挿入させることは可能ですか?

EDIT2:このアプローチは、Cade Rouxが以下に引用しているものと非常に似ているようです:

http://www.sqlteam.com/article/using-the-output-clause-to-capture-identity-values-on-multi-row-inserts

ただし、この記事では、作成者は魔法の一意の値「ProductNumber」を使用して、挿入された情報を「出力」値から元のテーブル値パラメーターに接続します。テーブルに魔法のような一意の値がない場合、これを行う方法を見つけようとしています。

4

2 に答える 2

6

TVP は、通常のテーブルと同様に順序付けられていないセットです。そのように指定した場合にのみ順序があります。ここで実際の順序を示す方法がないだけでなく、最後に ORDER BY なしで SELECT * を実行しているだけです。ここでどのような順序を期待していますか? 事実上、気にしないことを SQL Server に伝えました。そうは言っても、私はあなたのコードを実装し、行を正しい順序で戻すことに問題はありませんでした. どの ID 値がどのコメントに属しているかを実際に判断できるように、手順を少し変更しました。

DECLARE @TableOfIdentities TABLE (IdentValue INT, comment varchar(20))

INSERT INTO bulk_insert_test (comment)
OUTPUT Inserted.item_id, Inserted.comment 
INTO @TableOfIdentities(IdentValue, comment)
SELECT comment FROM @mytable

SELECT * FROM @TableOfIdentities

次に、このコードを使用して呼び出しました (これにはすべての C# は必要ありません)。

DECLARE @t bulk_insert_table_type;
INSERT @t VALUES(5,'foo'),(2,'bar'),(3,'zzz');
SELECT * FROM @t;

EXEC dbo.proc_bulk_insert_test @t;

結果:

1   foo
2   bar
3   zzz

出力が ID 割り当ての順序になっていることを確認したい場合 (順序付けされていない TVP と同じ「順序」であるとは限りません)、ORDER BY item_id手順の最後の選択に追加できます。

ID 値が重要な順序になるように宛先テーブルに挿入する場合は、いくつかのオプションがあります。

  • TVP に列を追加し、その列に順序を挿入してから、カーソルを使用してその順序で行を反復処理し、一度に 1 つずつ挿入します。行ごとにプロシージャ全体を呼び出すよりもさらに効率的です。

  • 順序を示す列を TVP に追加し、挿入で ORDER BY を使用します。これは保証されていませんが、特に MAXDOP 1 を使用して並列処理の問題を排除する場合は、比較的信頼性があります。

いずれにせよ、あなたはORDERに多くの関連性を置いているようです. あなたの注文は実際にはどういう意味ですか?順序に何らかの意味を持たせたい場合は、IDENTITY 列を使用してはいけません。

于 2012-08-23T17:32:41.780 に答える
3

ORDER BYこれにはnoを指定します。SELECT * FROM @TableOfIdentitiesしたがって、順序の保証はありません。それらが送信されたのと同じ順序でそれらが必要な場合は、行が送信された順序と一致するでINNER JOIN挿入されたデータにそれを実行します。ORDER BY

于 2012-08-23T17:28:28.810 に答える