Làm cách nào để xóa chỉ các bản ghi liên quan trong MERGE đa khóa trong SQL Server?


7

Giả sử bạn có một cái gì đó như thế này:

Bảng nguồn (biến):

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

Bảng mục tiêu:

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

Tôi muốn kết hợp Valuesthành Targetvới các nguyên tắc sau đây:

  • Phù hợp source.LeftId = target.LeftId AND source.RightId = target.RightId
    • khi khớp trong mục tiêu, cập nhật CustomValue
    • khi không khớp trong mục tiêu, chèn
  • Xóa bất kỳ giá trị chưa từng có trong các mục tiêu mà làm phù hợp với một LeftIdtrong nguồn, tức là hồ sơ duy nhất xóa mà liên quan đến LefIds của những gì tôi đang sáp nhập.

(Quy tắc cuối cùng đó thật khó diễn tả, xin lỗi!)

Ví dụ:

Nguồn:

1, 10, foo
1, 11, foo

Mục tiêu:

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

Kết quả hợp nhất:

Mục tiêu kết quả:

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

Vì thế...

Đây là những gì tôi có cho đến nay, nó quan tâm 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;

Làm thế nào để tôi thực hiện deletemột phần của quy tắc của tôi?


Không có "khớp trên một cột nhưng không phải là nhánh" khác và quy tắc "không khớp" mà bạn muốn thêm dường như dựa vào một tìm kiếm duy nhất trên các giá trị LeftID trong @Values. Tại sao không thực hiện thao tác này trong một tuyên bố thứ hai thay vì cố gắng đưa nó vào MERGE? Việc bảo vệ điều kiện đồng thời / cuộc đua không được đảm bảo trong MERGE mà không có GIỮ rõ ràng và nếu bạn tạo nhiều tuyên bố, bạn có thể đạt được hiệu quả tương tự với giao dịch gói.
Aaron Bertrand

1
@AaronBertrand, cảm giác như đó là con đường đúng đắn. Tôi nghĩ rằng tôi có thể đạt được hiệu quả tôi muốn với điều này, nhưng như bạn nói, nó có thể không phù hợp:WHEN NOT MATCHED BY source AND EXISTS(SELECT * FROM @Values M WHERE M.LeftId = target.LeftId) THEN DELETE;
Michael Haren

Câu trả lời:


6

Đây là DELETEhoạt động riêng biệt tôi có trong tâm trí:

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);

Như tôi phác thảo ở đây , đối với một liên kết chống bán bên trái, NOT EXISTSmẫu thường sẽ vượt trội hơn LEFT JOIN / NULLmẫu (nhưng bạn phải luôn kiểm tra).

Không chắc chắn nếu mục tiêu tổng thể của bạn là rõ ràng hoặc hiệu suất, vì vậy chỉ có bạn có thể đánh giá nếu điều này sẽ làm việc tốt hơn cho các yêu cầu của bạn hơn là NOT MATCHED BY sourcetùy chọn. Bạn sẽ phải xem xét các kế hoạch một cách định tính, và các kế hoạch và / hoặc số liệu thời gian chạy một cách định lượng, để biết chắc chắn.

Nếu bạn mong đợi MERGElệnh của bạn bảo vệ bạn khỏi các điều kiện chủng tộc sẽ xảy ra với nhiều tuyên bố độc lập, tốt hơn bạn nên đảm bảo đó là sự thật bằng cách thay đổi nó thành:

MERGE dbo.Mapping WITH (HOLDLOCK) AS target

(Từ bài đăng trên blog của Dan Guzman .)

Cá nhân, tôi sẽ làm tất cả những điều này mà không có MERGE, bởi vì có những lỗi chưa được giải quyết, trong số những lý do khác . Và Paul White dường như cũng đề xuất các tuyên bố DML riêng biệt .

Và đây là lý do tại sao tôi đã thêm một tiền tố schema: bạn nên luôn luôn tham khảo đối tượng bằng lược đồ, khi tạo, ảnh hưởng, vv .


9

Bạn có thể lọc ra các hàng bạn cần xem xét từ bảng mục tiêu trong CTE và sử dụng CTE làm mục tiêu trong hợp nhất.

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
;

+1 cách giải quyết tốt đẹp (rừng cho cây) và cung cấp nguồn tốt.
Aaron Bertrand

2
Sử dụng CTE cho việc này được cảnh báo chống lại BOL rõ ràng mặc dù tôi không rõ chính xác khi nào nó có thể hoạt động khác với quan điểm.
Martin Smith

@MartinSmith Điều đó có hơi lạ không? Tôi nghĩ rằng quan điểm của CTE mở rộng vào truy vấn theo cùng một cách. Rõ ràng có một số khác biệt trong cách họ được đối xử.
Mikael Eriksson

@MikaelEriksson - Tôi đã coi chúng là có thể hoán đổi cho nhau trước khi đọc nó.
Martin Smith

@MartinSmith BOL mô tả các thực tiễn tốt nhất để bạn không mắc phải những lỗi ngớ ngẩn khi viết tuyên bố hợp nhất. Khi bạn nhận ra rằng mệnh đề on là một tham gia bên ngoài đầy đủ, bạn hiểu tại sao những điều kỳ lạ xảy ra khi bạn lọc các hằng số trong mệnh đề on. Điều tương tự với CTE. Nó không thể làm bất cứ điều gì bạn không yêu cầu nó làm nhưng thật khó để phát hiện ra những trường hợp bạn đã làm hỏng. Điều được mô tả là một vấn đề với CTE là khi bạn lọc ra các hàng từ mục tiêu phù hợp với nguồn và sẽ ngăn không cho chèn.
Mikael Eriksson

3

Bạn có thể sử dụng WHEN NOT MATCHED BY SOURCEmệnh đề và cung cấp một điều kiện bổ sung với nó như thế này:

Câu đố SQL

Thiết lập lược đồ 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');

Truy vấn 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;

Kết quả :

| 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 |

Truy vấn 2 :

SELECT * FROM dbo.Mapping;

Kết quả :

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

Tôi đã thêm mệnh đề đầu ra vào MERGEcâu lệnh để hiển thị hành động nào được thực hiện cho mỗi hàng.

Như những người khác đã nhận xét, bạn cũng cần cung cấp WITH(HOLDLOCK)gợi ý trên bảng mục tiêu để ngăn chặn điều kiện cuộc đua.


1

Đây là những gì tôi nghĩ ra. Bất kỳ thông tin phản hồi được đánh giá cao!

- Dữ liệu kiểm tra:

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')

- Cách 1: Xử lý deletephần riêng:

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;

- Cách 2: Thực hiện (lúng túng?) Trong MERGEtuyên bố:

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;

- Kiểm tra kết quả:

SELECT * FROM @Mappings ORDER BY LeftId, RightId
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.