web-dev-qa-db-ja.com

INSERTのOUTPUT句の順序に依存しても安全ですか?

この表を考えると:

CREATE TABLE dbo.Target (
   TargetId int identity(1, 1) NOT NULL,
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL, -- of course this should be normalized
   Code int NOT NULL,
   CONSTRAINT PK_Target PRIMARY KEY CLUSTERED (TargetId)
);

2つのわずかに異なるシナリオで、行を挿入してID列から値を返します。

シナリオ1

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
   (VALUES
      ('Blue', 'New', 1234),
      ('Blue', 'Cancel', 4567),
      ('Red', 'New', 5678)
   ) t (Color, Action, Code)
;

シナリオ2

CREATE TABLE #Target (
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL,
   Code int NOT NULL,
   PRIMARY KEY CLUSTERED (Color, Action)
);

-- Bulk insert to the table the same three rows as above by any means

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM #Target
;

質問

dbo.Targetテーブル挿入から返されたID値を、1)VALUES句および2)#Targetテーブルに存在する順序で返すことを信頼できますか?出力行セットでの位置によってそれらを元の入力に関連付けますか?

参考のために

これは、アプリケーションで何が起こっているかを示すいくつかのトリミングされたC#コードです(シナリオ1、すぐにSqlBulkCopyを使用するように変換されます)。

public IReadOnlyCollection<Target> InsertTargets(IEnumerable<Target> targets) {
   var targetList = targets.ToList();
   const string insertSql = @"
      INSERT dbo.Target (
         CoreItemId,
         TargetDateTimeUtc,
         TargetTypeId,
      )
      OUTPUT
         Inserted.TargetId
      SELECT
         input.CoreItemId,
         input.TargetDateTimeUtc,
         input.TargetTypeId,
      FROM
         (VALUES
            {0}
         ) input (
            CoreItemId,
            TargetDateTimeUtc,
            TargetTypeId
         );";
   var results = Connection.Query<DbTargetInsertResult>(
      string.Format(
         insertSql,
         string.Join(
            ", ",
            targetList
               .Select(target => $@"({target.CoreItemId
                  }, '{target.TargetDateTimeUtc:yyyy-MM-ddTHH:mm:ss.fff
                  }', {(byte) target.TargetType
                  })";
               )
         )
      )
      .ToList();
   return targetList
      .Zip( // The correlation that relies on the order of the two inputs being the same
         results,
         (inputTarget, insertResult) => new Target(
            insertResult.TargetId, // with the new TargetId to replace null.
            inputTarget.TargetDateTimeUtc,
            inputTarget.CoreItemId,
            inputTarget.TargetType
         )
      )
      .ToList()
      .AsReadOnly();
}
20
ErikE

Dbo.Targetテーブル挿入から返されたID値を1)VALUES句および2)#Targetテーブルに存在する順序で返すことを信頼できますか?これにより、出力行セット内の位置によってそれらを関連付けることができます。元の入力に?

いいえ、実際に文書化された保証がなければ、保証されるものに依存することはできません。ドキュメント 明示的に述べています そのような保証はありません。

SQL Serverは、OUTPUT句を使用したDMLステートメントによって行が処理および返される順序を保証しません。必要なセマンティクスを保証できる適切なWHERE句を含めるか、複数の行がDML操作に適格である場合、順序が保証されないことを理解するのはアプリケーションの責任です。

これは多くの文書化されていない仮定に依存します

  1. 行が定数スキャンから出力される順序は、values句と同じ順序です(これらが異なることは一度もありませんが、これは保証されていません)。
  2. 行が挿入される順序は、定数スキャンから出力される順序と同じになります(常にそうであるとは限りません)。
  3. 「ワイド」(インデックスごと)の実行プランを使用している場合、出力句の値は、セカンダリインデックスの値ではなく、クラスター化インデックスの更新演算子から取得されます。
  4. 注文がその後も保持されることが保証されていること。 when 行をネットワーク経由で送信するためにパッケージ化する
  5. 順序が予測可能であるように見えても、並列挿入などの機能への実装変更は将来順序を変更しません(現在 OUTPUT句がINSERT…SELECTステートメントで指定され、クライアントに結果を返す場合、その後、INSERTを含む一般的に並列プランは無効になります

ポイント2が失敗する例((Color, Action)_のクラスター化されたPKを想定)は、VALUES句に600行を追加すると表示されます。次に、プランの挿入の前に並べ替え演算子があるため、VALUES句の元の順序が失われます。

目標を達成するための文書化された方法がありますが、これはソースに番号付けを追加し、MERGEの代わりにINSERTを使用することです

_MERGE dbo.Target
USING (VALUES (1, 'Blue', 'New', 1234),
              (2, 'Blue', 'Cancel', 4567),
              (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ON 1 = 0
WHEN NOT MATCHED THEN
  INSERT (Color,
          Action,
          Code)
  VALUES (Color,
          Action,
          Code)
OUTPUT t.SourceId,
       inserted.TargetId; 
_

enter image description here

@ a_horse_with_no_name

マージは本当に必要ですか? insert into ... select ... from (values (..)) t (...) order by sourceidを実行できませんか?

はい、できます。 SQL Serverでの順序付けの保証… は、

SELECTとORDER BYを使用して行にデータを入力するINSERTクエリは、ID値の計算方法を保証しますが、行が挿入される順序は保証しません

だからあなたは使うことができます

_INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
(VALUES (1, 'Blue', 'New', 1234),
        (2, 'Blue', 'Cancel', 4567),
        (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ORDER BY t.SourceId
_

enter image description here

これは、ID値が_t.SourceId_の順序で割り当てられることを保証しますが、特定の順序で出力されること、または割り当てられたID列の値にギャップがないことを保証します(たとえば、同時挿入が試行された場合)。

23
Martin Smith