MERGE một tập hợp con của bảng mục tiêu


71

Tôi đang cố gắng sử dụng một MERGEcâu lệnh để chèn hoặc xóa các hàng khỏi một bảng, nhưng tôi chỉ muốn hành động trên một tập hợp con của các hàng đó. Các tài liệu cho MERGEcó một cảnh báo từ khá mạnh mẽ:

Điều quan trọng là chỉ định các cột từ bảng mục tiêu được sử dụng cho mục đích phù hợp. Nghĩa là, chỉ định các cột từ bảng đích được so sánh với cột tương ứng của bảng nguồn. Không cố gắng cải thiện hiệu năng truy vấn bằng cách lọc ra các hàng trong bảng đích trong mệnh đề ON, chẳng hạn như bằng cách chỉ định AND NOT target_table.column_x = value. Làm như vậy có thể trả lại kết quả bất ngờ và không chính xác.

nhưng đây chính xác là những gì tôi phải làm để thực hiện MERGEcông việc của mình.

Dữ liệu tôi có là một bảng mục tiêu nhiều-nhiều-tham gia tiêu chuẩn cho các danh mục (ví dụ: các mục được bao gồm trong các danh mục) như thế nào:

CategoryId   ItemId
==========   ======
1            1
1            2
1            3
2            1
2            3
3            5
3            6
4            5

Những gì tôi cần làm là thay thế hiệu quả tất cả các hàng trong một danh mục cụ thể bằng một danh sách mới của các mục. Nỗ lực ban đầu của tôi để làm điều này trông như thế này:

MERGE INTO CategoryItem AS TARGET
USING (
  SELECT ItemId FROM SomeExternalDataSource WHERE CategoryId = 2
) AS SOURCE
ON SOURCE.ItemId = TARGET.ItemId AND TARGET.CategoryId = 2
WHEN NOT MATCHED BY TARGET THEN
    INSERT ( CategoryId, ItemId )
    VALUES ( 2, ItemId )
WHEN NOT MATCHED BY SOURCE AND TARGET.CategoryId = 2 THEN
    DELETE ;

Điều này dường như đang làm việc trong các thử nghiệm của tôi, nhưng tôi đang làm chính xác những gì MSDN cảnh báo rõ ràng tôi không nên làm. Điều này khiến tôi lo ngại rằng tôi sẽ gặp phải sự cố không mong muốn sau này, nhưng tôi không thể thấy bất kỳ cách nào khác để khiến tôi MERGEchỉ ảnh hưởng đến các hàng với giá trị trường cụ thể ( CategoryId = 2) và bỏ qua các hàng từ các danh mục khác.

Có cách nào "chính xác hơn" để đạt được kết quả tương tự không? Và "kết quả bất ngờ hay không chính xác" mà MSDN đang cảnh báo tôi là gì?


Có, tài liệu sẽ hữu ích hơn nếu nó có một ví dụ cụ thể về "kết quả không mong muốn và không chính xác".
AK

3
@AlexKuznetsov Có một ví dụ ở đây .
Paul White

@QueryKiwi cảm ơn bạn về liên kết - IMO tài liệu sẽ tốt hơn nhiều nếu được giới thiệu từ trang gốc.
AK

1
@AlexKuznetsov Đồng ý. Thật không may, việc tái tổ chức BOL cho năm 2012 đã phá vỡ điều đó, trong số nhiều thứ khác. Nó được liên kết khá độc đáo trong tài liệu 2008 R2.
Paul White

@AK mssqltips.com/sqlservertip/3074/ Ấn
John Zabroski

Câu trả lời:


103

Câu MERGElệ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 WHENmệ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à: Kế hoạch hợp nhất

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 = 2sẽ 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 ONmệ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 ONmệnh đề nối ngoài so với khi chúng được chỉ định trong WHEREmệnh đề. Các MERGEcú 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 MERGEcú 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 WHENmệ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 ONmệ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:

Hợp nhất kế hoạch 2

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 MERGEcú pháp sai này .

Đôi khi, việc viết các MERGEhoạ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;

Tôi biết đây là một câu hỏi thực sự cũ ... nhưng bất kỳ cơ hội nào bạn có thể giải thích về "Sử dụng biểu thức bảng chung mang lại rủi ro tương tự để thêm các vị từ vào mệnh đề ON, nhưng vì những lý do hơi khác nhau." Tôi biết BOL cũng có một cảnh báo mơ hồ tương tự "Phương pháp này tương tự như chỉ định các tiêu chí tìm kiếm bổ sung trong mệnh đề ON và có thể tạo ra kết quả không chính xác. Chúng tôi khuyên bạn nên tránh sử dụng phương pháp này ...". Phương pháp CTE xuất hiện để giải quyết trường hợp sử dụng của tôi, tuy nhiên tôi tự hỏi liệu có kịch bản nào tôi không xem xét không.
Henry Lee
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.