MERGE
声明は、複雑な構文、さらに複雑な実装を持っていますが、基本的な考え方は、2つのテーブルを結合する変更(挿入、更新、または削除)する必要がある行にダウンフィルタリングすることで、その後、要求された変更を実行します。次のサンプルデータがあるとします。
DECLARE @CategoryItem AS TABLE
(
CategoryId integer NOT NULL,
ItemId integer NOT NULL,
PRIMARY KEY (CategoryId, ItemId),
UNIQUE (ItemId, CategoryId)
);
DECLARE @DataSource AS TABLE
(
CategoryId integer NOT NULL,
ItemId integer NOT NULL
PRIMARY KEY (CategoryId, ItemId)
);
INSERT @CategoryItem
(CategoryId, ItemId)
VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 1),
(2, 3),
(3, 5),
(3, 6),
(4, 5);
INSERT @DataSource
(CategoryId, ItemId)
VALUES
(2, 2);
ターゲット
╔════════════╦════════╗
║ CategoryId ║ ItemId ║
╠════════════╬════════╣
║ 1 ║ 1 ║
║ 2 ║ 1 ║
║ 1 ║ 2 ║
║ 1 ║ 3 ║
║ 2 ║ 3 ║
║ 3 ║ 5 ║
║ 4 ║ 5 ║
║ 3 ║ 6 ║
╚════════════╩════════╝
ソース
╔════════════╦════════╗
║ CategoryId ║ ItemId ║
╠════════════╬════════╣
║ 2 ║ 2 ║
╚════════════╩════════╝
望ましい結果は、ターゲットのデータをソースのデータに置き換えることですが、これはのみですCategoryId = 2
。MERGE
上記の説明に従って、キーのみでソースとターゲットを結合するクエリを記述し、WHEN
句でのみ行をフィルタリングする必要があります。
MERGE INTO @CategoryItem AS TARGET
USING @DataSource AS SOURCE ON
SOURCE.ItemId = TARGET.ItemId
AND SOURCE.CategoryId = TARGET.CategoryId
WHEN NOT MATCHED BY SOURCE
AND TARGET.CategoryId = 2
THEN DELETE
WHEN NOT MATCHED BY TARGET
AND SOURCE.CategoryId = 2
THEN INSERT (CategoryId, ItemId)
VALUES (CategoryId, ItemId)
OUTPUT
$ACTION,
ISNULL(INSERTED.CategoryId, DELETED.CategoryId) AS CategoryId,
ISNULL(INSERTED.ItemId, DELETED.ItemId) AS ItemId
;
これにより、次の結果が得られます。
╔═════════╦════════════╦════════╗
║ $ACTION ║ CategoryId ║ ItemId ║
╠═════════╬════════════╬════════╣
║ DELETE ║ 2 ║ 1 ║
║ INSERT ║ 2 ║ 2 ║
║ DELETE ║ 2 ║ 3 ║
╚═════════╩════════════╩════════╝
╔════════════╦════════╗
║ CategoryId ║ ItemId ║
╠════════════╬════════╣
║ 1 ║ 1 ║
║ 1 ║ 2 ║
║ 1 ║ 3 ║
║ 2 ║ 2 ║
║ 3 ║ 5 ║
║ 3 ║ 6 ║
║ 4 ║ 5 ║
╚════════════╩════════╝
実行計画は次のとおりです。
両方のテーブルが完全にスキャンされていることに注意してください。CategoryId = 2
ターゲットテーブルで影響を受けるのは行のみであるため、これは非効率的であると考えるかもしれません。これが、Books Onlineの警告の出番です。ターゲットの必要な行のみをタッチするように最適化しようとする誤った試みの1つは次のとおりです。
MERGE INTO @CategoryItem AS TARGET
USING
(
SELECT CategoryId, ItemId
FROM @DataSource AS ds
WHERE CategoryId = 2
) AS SOURCE ON
SOURCE.ItemId = TARGET.ItemId
AND TARGET.CategoryId = 2
WHEN NOT MATCHED BY TARGET THEN
INSERT (CategoryId, ItemId)
VALUES (CategoryId, ItemId)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT
$ACTION,
ISNULL(INSERTED.CategoryId, DELETED.CategoryId) AS CategoryId,
ISNULL(INSERTED.ItemId, DELETED.ItemId) AS ItemId
;
ON
句のロジックは、結合の一部として適用されます。この場合、結合は完全な外部結合です(理由については、このBooks Onlineのエントリを参照してください)。外部結合の一部としてターゲット行にカテゴリ2のチェックを適用すると、最終的に異なる値を持つ行が削除されます(ソースと一致しないため):
╔═════════╦════════════╦════════╗
║ $ACTION ║ CategoryId ║ ItemId ║
╠═════════╬════════════╬════════╣
║ DELETE ║ 1 ║ 1 ║
║ DELETE ║ 1 ║ 2 ║
║ DELETE ║ 1 ║ 3 ║
║ DELETE ║ 2 ║ 1 ║
║ INSERT ║ 2 ║ 2 ║
║ DELETE ║ 2 ║ 3 ║
║ DELETE ║ 3 ║ 5 ║
║ DELETE ║ 3 ║ 6 ║
║ DELETE ║ 4 ║ 5 ║
╚═════════╩════════════╩════════╝
╔════════════╦════════╗
║ CategoryId ║ ItemId ║
╠════════════╬════════╣
║ 2 ║ 2 ║
╚════════════╩════════╝
根本原因は、述部が外部結合ON
句で動作するのと、句で指定されている場合と異なる理由WHERE
です。MERGE
構文(および指定された句に応じて参加実装は)ちょうどそれが難しく、これがそうであることを確認するために作ります。
Books Onlineのガイダンス(パフォーマンスの最適化エントリで拡張)はMERGE
、ユーザーが必ずしもすべての実装の詳細を理解したり、オプティマイザーが正当に再配置する方法を説明したりせずに、構文を使用して正しいセマンティックを表現することを保証するガイダンスを提供します実行効率上の理由によるもの。
ドキュメントには、早期フィルタリングを実装するための3つの潜在的な方法があります。
WHEN
句にフィルタリング条件を指定すると、正しい結果が保証されますが、厳密に必要な行よりも多くの行がソーステーブルとターゲットテーブルから読み取られて処理されることを意味する場合があります(最初の例を参照)。
フィルター条件を含むビューを介して更新することも正しい結果を保証します(変更された行はビューを介して更新するためにアクセス可能である必要があるため)が、これには専用ビューが必要です。
共通テーブル式を使用すると、述部をON
句に追加する場合と同様のリスクが伴いますが、理由はわずかに異なります。多くの場合、それは安全ですが、これを確認するには実行計画の専門家による分析が必要です(そして広範な実地テスト)。例えば:
WITH TARGET AS
(
SELECT *
FROM @CategoryItem
WHERE CategoryId = 2
)
MERGE INTO TARGET
USING
(
SELECT CategoryId, ItemId
FROM @DataSource
WHERE CategoryId = 2
) AS SOURCE ON
SOURCE.ItemId = TARGET.ItemId
AND SOURCE.CategoryId = TARGET.CategoryId
WHEN NOT MATCHED BY TARGET THEN
INSERT (CategoryId, ItemId)
VALUES (CategoryId, ItemId)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT
$ACTION,
ISNULL(INSERTED.CategoryId, DELETED.CategoryId) AS CategoryId,
ISNULL(INSERTED.ItemId, DELETED.ItemId) AS ItemId
;
これにより、より最適な計画で正しい結果(繰り返されない)が生成されます。
プランは、ターゲットテーブルからカテゴリ2の行のみを読み取ります。ターゲットテーブルが大きい場合、これはパフォーマンスの重要な考慮事項になる可能性がありますが、MERGE
構文を使用してこの間違いを取得するのは非常に簡単です。
時には、MERGE
個別のDML操作として記述する方が簡単です。このアプローチは、1つよりも優れたパフォーマンスを発揮できMERGE
ます。
DELETE ci
FROM @CategoryItem AS ci
WHERE ci.CategoryId = 2
AND NOT EXISTS
(
SELECT 1
FROM @DataSource AS ds
WHERE
ds.ItemId = ci.ItemId
AND ds.CategoryId = ci.CategoryId
);
INSERT @CategoryItem
SELECT
ds.CategoryId,
ds.ItemId
FROM @DataSource AS ds
WHERE
ds.CategoryId = 2;