この表を考えると:
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列から値を返します。
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)
;
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();
}
Dbo.Targetテーブル挿入から返されたID値を1)VALUES句および2)#Targetテーブルに存在する順序で返すことを信頼できますか?これにより、出力行セット内の位置によってそれらを関連付けることができます。元の入力に?
いいえ、実際に文書化された保証がなければ、保証されるものに依存することはできません。ドキュメント 明示的に述べています そのような保証はありません。
SQL Serverは、OUTPUT句を使用したDMLステートメントによって行が処理および返される順序を保証しません。必要なセマンティクスを保証できる適切なWHERE句を含めるか、複数の行がDML操作に適格である場合、順序が保証されないことを理解するのはアプリケーションの責任です。
これは多くの文書化されていない仮定に依存します
ポイント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;
_
マージは本当に必要ですか?
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列の値にギャップがないことを保証します(たとえば、同時挿入が試行された場合)。