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


19

この表が与えられた場合:

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.Target1)VALUES句と2)#Targetテーブルに存在する順序で返されるテーブル挿入から返されたID値に依存して、出力行セット内の位置によって元の入力に戻すことができますか?

参考のために

以下は、アプリケーションで何が起きているかを示す、一部を切り詰めた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();
}

回答:


22

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

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

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

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

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

句に(Color, Action)600行を追加すると、ポイント2の失敗の例(クラスター化されたPKを想定)を見ることができますVALUES。次に、プランには挿入の前にソート演算子があるため、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; 

ここに画像の説明を入力してください

@無名の馬

マージは本当に必要ですか?ただやれませ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

ここに画像の説明を入力してください

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


2
ギャップの可能性と出力が特定の順序になっていないことについてのこの最後のビットは、入力との相関を取り戻そうとする場合に物事をもう少し興味深いものにします。アプリケーションでのbyが仕事をすることになると思いますが、を使用する方が安全で明確なようMERGEです。
エリック

OUTPUT ... INTO [#temp]次に構文を使用してSELECT ... FROM [#temp] ORDER BY、出力順序を保証します。
マックスヴァーノン

TL; DRバージョン:SQL Serverでは、ORDER BY句がない限り、SQLの実装は一般的に信じられていますが、順序は保証されません。
ナテイルビン

Re。ギャップ:ギャップを防ぐためにInsert声明を囲みTransactionますか?
トム
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.