web-dev-qa-db-ja.com

SQL ServerでマルチキーMERGEの関連レコードのみを削除するにはどうすればよいですか?

次のようなものがあるとします。

ソーステーブル(変数):

Values (
  LeftId INT NOT NULL,
  RightId INT NOT NULL,
  CustomValue varchar(100) NULL
)

ターゲットテーブル:

Mapping (
  LeftId INT NOT NULL,
  RightId INT NOT NULL,
  CustomValue varchar(100) NULL
)

以下のルールでValuesTargetにマージしたい:

  • source.LeftId = target.LeftId AND source.RightId = target.RightId に一致
    • ターゲットで一致したら、CustomValueを更新します
    • ターゲットで一致しない場合、挿入
  • doがソースのLeftIdと一致するターゲット内の一致しない値を削除します。つまり、LefIds私がマージしているもの。

(その最後のルールは説明するのが難しい、ごめんなさい!)

例えば:

ソース:

1, 10, foo
1, 11, foo

ターゲット:

1, 10, bar
1, 12, foo
2, 20, car

マージ結果:

結果ターゲット:

1, 10, foo (updated)
1, 11, foo (inserted)
1, 12, foo (deleted)
2, 20, car (unchanged)

そう...

これが私がこれまで持ってきたもので、updateinsertを処理します:

MERGE Mapping AS target
USING (SELECT LeftId, RightId, CustomValue FROM @Values) 
  AS source (LeftId, RightId, CustomValue)
  ON target.LeftId = source.LeftId
    AND target.RightId = source.RightId
WHEN NOT MATCHED THEN
  INSERT (LeftId, RightId, CustomValue)
  VALUES (source.LeftId, source.RightId, source.CustomValue)
WHEN MATCHED THEN
  UPDATE SET
    CustomValue = source.CustomValue;

ルールのdelete部分をどのように実行しますか?

7
Michael Haren

これは、私が考えていた個別のDELETE操作です。

DELETE m
FROM dbo.Mapping AS m
WHERE EXISTS 
  (SELECT 1 FROM @Values WHERE LeftID = m.LeftID)
AND NOT EXISTS 
  (SELECT 1 FROM @Values WHERE LeftID = m.LeftID AND RightID = m.RightID);

私が ここでの概要、左の反セミ結合の場合)は、NOT EXISTSパターンがLEFT JOIN / NULLパターンよりも多くの場合 よりも優れています。

全体的な目標が明快さかパフォーマンスかはわからないため、NOT MATCHED BY sourceオプションよりも要件に適しているかどうかを判断できるのはあなただけです。確実に知るためには、計画を定性的に、計画やランタイムメトリックを定量的に調べる必要があります。

MERGEコマンドが複数の独立したステートメントで発生する競合状態から保護することを期待している場合は、次のように変更して、trueであることを確認してください。

MERGE dbo.Mapping WITH (HOLDLOCK) AS target

Dan Guzmanのブログ投稿 から。)

個人的には、MERGEを使わずにこれをすべて実行します他の理由の中で、未解決のバグがあるため 。そして Paul Whiteは別のDMLステートメントも推奨するようです

そして、これが私がスキーマ接頭辞を追加した理由です: 作成、影響を与えるときなど、常にスキーマによってオブジェクトを参照する必要があります

6
Aaron Bertrand

CTEのターゲットテーブルから考慮する必要のある行を除外し、CTEをマージのターゲットとして使用できます。

WITH T AS
(
  SELECT M.LeftId, 
         M.RightId, 
         M.CustomValue
  FROM @Mappings AS M
  WHERE EXISTS (SELECT *
                FROM @Values AS V
                WHERE M.LeftId = V.LeftId) 
)
MERGE T
USING @Values AS S
ON T.LeftId = S.LeftId and
   T.RightId = S.RightId
WHEN NOT MATCHED BY TARGET THEN
  INSERT (LeftId, RightId, CustomValue) 
  VALUES (S.LeftId, S.RightId, S.CustomValue)
WHEN MATCHED THEN
  UPDATE SET CustomValue = S.CustomValue
WHEN NOT MATCHED BY SOURCE THEN
  DELETE
;
9
Mikael Eriksson

_WHEN NOT MATCHED BY SOURCE_句を使用して、次のように条件を追加できます。

SQLフィドル

MS SQL Server 2008スキーマセットアップ

_CREATE TABLE dbo.Vals (
  LeftId INT NOT NULL,
  RightId INT NOT NULL,
  CustomValue varchar(100) NULL
);

CREATE TABLE dbo.Mapping (
  LeftId INT NOT NULL,
  RightId INT NOT NULL,
  CustomValue varchar(100) NULL
);

INSERT INTO dbo.Vals(LeftId,RightId,CustomValue)
VALUES(1, 10, 'foo10'),(1, 11, 'foo11');

INSERT INTO dbo.Mapping(LeftId,RightId,CustomValue)
VALUES(1, 10, 'bar'),(1, 12, 'foo'),(2, 20, 'car');
_

クエリ1

_MERGE dbo.Mapping WITH(HOLDLOCK) AS target
USING (SELECT LeftId, RightId, CustomValue FROM dbo.Vals) 
  AS source (LeftId, RightId, CustomValue)
  ON target.LeftId = source.LeftId
    AND target.RightId = source.RightId
WHEN NOT MATCHED THEN
  INSERT (LeftId, RightId, CustomValue)
  VALUES (source.LeftId, source.RightId, source.CustomValue)
WHEN MATCHED THEN
  UPDATE SET
    CustomValue = source.CustomValue


WHEN NOT MATCHED BY SOURCE AND EXISTS(SELECT 1 FROM dbo.Vals iVals WHERE target.LeftId = iVals.LeftId) THEN
  DELETE



OUTPUT $action AS Action,
       INSERTED.LeftId AS INS_LeftId,INSERTED.RightId AS INS_RightId,INSERTED.CustomValue AS INS_Val,
       DELETED.LeftId AS DEL_LeftId,DELETED.RightId AS DEL_RightId,DELETED.CustomValue AS DEL_Val;
_

結果

_| ACTION | INS_LEFTID | INS_RIGHTID | INS_VAL | DEL_LEFTID | DEL_RIGHTID | DEL_VAL |
------------------------------------------------------------------------------------
| INSERT |          1 |          11 |   foo11 |     (null) |      (null) |  (null) |
| UPDATE |          1 |          10 |   foo10 |          1 |          10 |     bar |
| DELETE |     (null) |      (null) |  (null) |          1 |          12 |     foo |
_

クエリ2

_SELECT * FROM dbo.Mapping;
_

結果

_| LEFTID | RIGHTID | CUSTOMVALUE |
----------------------------------
|      1 |      10 |       foo10 |
|      2 |      20 |         car |
|      1 |      11 |       foo11 |
_

各行に対して実行されたアクションを示すために、MERGEステートメントに出力句を追加しました。

他のユーザーがコメントしているように、競合状態を防ぐために、ターゲットテーブルにWITH(HOLDLOCK)ヒントも提供する必要があります。

3
Sebastian Meine

これが私が思いついたものです。フィードバックは大歓迎です!

- テストデータ:

DECLARE @Values   TABLE(LeftId INT, RightId INT, CustomValue VARCHAR(100))
DECLARE @Mappings TABLE(LeftId INT, RightId INT, CustomValue VARCHAR(100))

-- the incoming values
INSERT INTO @Values   VALUES (1, 10, 'bar2'), (1, 11, 'foo')

-- the existing table
INSERT INTO @Mappings VALUES (1, 10, 'bar'),  (1, 12, 'foo'), (2, 20, 'car')

-オプション1:deleteパーツを個別に処理します。

DELETE M
FROM @Mappings M
JOIN (SELECT DISTINCT LeftId FROM @Values) DistinctLeftIds ON M.LeftId = DistinctLeftIds.LeftId 
LEFT JOIN @Values V ON M.LeftId = V.LeftId AND M.RightId = V.RightId
WHERE V.LeftId IS NULL

MERGE @Mappings AS target
USING (SELECT LeftId, RightId, CustomValue FROM @Values) 
  AS source (LeftId, RightId, CustomValue)
  ON target.LeftId = source.LeftId
    AND target.RightId = source.RightId
WHEN NOT MATCHED THEN
  INSERT (LeftId, RightId, CustomValue)
  VALUES (source.LeftId, source.RightId, source.CustomValue)
WHEN MATCHED THEN
  UPDATE SET
    CustomValue = source.CustomValue;

-オプション2:MERGEステートメントで(ぎこちなく?)

MERGE @Mappings AS target
USING (SELECT LeftId, RightId, CustomValue FROM @Values) 
  AS source (LeftId, RightId, CustomValue)
  ON target.LeftId = source.LeftId
    AND target.RightId = source.RightId
WHEN NOT MATCHED THEN
  INSERT (LeftId, RightId, CustomValue)
  VALUES (source.LeftId, source.RightId, source.CustomValue)
WHEN MATCHED THEN
  UPDATE SET
    CustomValue = source.CustomValue;
WHEN NOT MATCHED BY source 
    AND EXISTS(SELECT * FROM @Values M WHERE M.LeftId = target.LeftId) THEN
  DELETE;

-結果を確認します。

SELECT * FROM @Mappings ORDER BY LeftId, RightId
1
Michael Haren