Câu MERGE
lệnh có một cú pháp phức tạp và việc triển khai thậm chí còn phức tạp hơn, nhưng về cơ bản, ý tưởng là tham gia hai bảng, lọc xuống các hàng cần thay đổi (chèn, cập nhật hoặc xóa), sau đó thực hiện các thay đổi được yêu cầu. Cho dữ liệu mẫu sau:
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);
Mục tiêu
╔════════════╦════════╗
║ CategoryId ║ ItemId ║
╠════════════╬════════╣
║ 1 ║ 1 ║
║ 2 ║ 1 ║
║ 1 ║ 2 ║
║ 1 ║ 3 ║
║ 2 ║ 3 ║
║ 3 ║ 5 ║
║ 4 ║ 5 ║
║ 3 ║ 6 ║
╚════════════╩════════╝
Nguồn
╔════════════╦════════╗
║ CategoryId ║ ItemId ║
╠════════════╬════════╣
║ 2 ║ 2 ║
╚════════════╩════════╝
Kết quả mong muốn là thay thế dữ liệu trong mục tiêu bằng dữ liệu từ nguồn, nhưng chỉ cho CategoryId = 2
. Theo mô tả MERGE
ở trên, chúng ta nên viết một truy vấn chỉ tham gia nguồn và đích trên các khóa và chỉ lọc các hàng trong WHEN
mệnh đề:
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
;
Điều này cho kết quả như sau:
╔═════════╦════════════╦════════╗
║ $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 ║
╚════════════╩════════╝
Kế hoạch thực hiện là:
Lưu ý cả hai bảng được quét đầy đủ. Chúng tôi có thể nghĩ rằng điều này không hiệu quả, bởi vì chỉ các hàng CategoryId = 2
sẽ bị ảnh hưởng trong bảng mục tiêu. Đây là lúc các cảnh báo trong Books Online xuất hiện. Một nỗ lực sai lầm để tối ưu hóa chỉ chạm vào các hàng cần thiết trong mục tiêu là:
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
;
Logic trong ON
mệnh đề được áp dụng như là một phần của phép nối. Trong trường hợp này, tham gia là một tham gia bên ngoài đầy đủ (xem mục Sách trực tuyến này để biết lý do). Áp dụng kiểm tra cho loại 2 trên các hàng đích như một phần của liên kết ngoài cuối cùng sẽ dẫn đến các hàng có giá trị khác bị xóa (vì chúng không khớp với nguồn):
╔═════════╦════════════╦════════╗
║ $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 ║
╚════════════╩════════╝
Nguyên nhân gốc là cùng một lý do vị ngữ hành xử khác nhau trong ON
mệnh đề nối ngoài so với khi chúng được chỉ định trong WHERE
mệnh đề. Các MERGE
cú pháp (và thực hiện tham gia tùy thuộc vào các điều khoản quy định) chỉ làm cho nó khó khăn hơn để thấy rằng đây là như vậy.
Các hướng dẫn trong cuốn sách trực tuyến (mở rộng trong Performance Tối ưu hóa nhập) cung cấp hướng dẫn mà sẽ đảm bảo ngữ nghĩa chính xác được thể hiện bằng MERGE
cú pháp, mà không cần người sử dụng nhất thiết phải hiểu tất cả các chi tiết thực hiện, hoặc tài khoản cho những cách thức mà tôi ưu hoa hợp pháp có thể sắp xếp lại những điều cho lý do hiệu quả thực hiện.
Tài liệu này cung cấp ba cách tiềm năng để thực hiện lọc sớm:
Việc chỉ định một điều kiện lọc trong WHEN
mệnh đề đảm bảo kết quả chính xác, nhưng có thể có nghĩa là nhiều hàng được đọc và xử lý từ các bảng nguồn và đích hơn là rất cần thiết (như đã thấy trong ví dụ đầu tiên).
Cập nhật thông qua chế độ xem chứa điều kiện lọc cũng đảm bảo kết quả chính xác (vì các hàng thay đổi phải có thể truy cập để cập nhật qua chế độ xem) nhưng điều này đòi hỏi phải có chế độ xem chuyên dụng và một điều kiện tuân theo các điều kiện kỳ lạ để cập nhật chế độ xem.
Sử dụng một biểu thức bảng chung mang lại những rủi ro tương tự để thêm các vị từ vào ON
mệnh đề, nhưng vì những lý do hơi khác nhau. Trong nhiều trường hợp, nó sẽ an toàn, nhưng nó đòi hỏi phân tích của chuyên gia về kế hoạch thực hiện để xác nhận điều này (và thử nghiệm thực tế sâu rộng). Ví dụ:
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
;
Điều này tạo ra kết quả chính xác (không lặp lại) với một kế hoạch tối ưu hơn:
Kế hoạch chỉ đọc các hàng cho loại 2 từ bảng mục tiêu. Đây có thể là một xem xét hiệu suất quan trọng nếu bảng mục tiêu lớn, nhưng quá dễ để sử dụng MERGE
cú pháp sai này .
Đôi khi, việc viết các MERGE
hoạt động DML riêng biệt sẽ dễ dàng hơn . Cách tiếp cận này thậm chí có thể thực hiện tốt hơn một lần duy nhất MERGE
, một thực tế thường làm mọi người ngạc nhiên.
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;