MERGE
คำสั่งมีไวยากรณ์ที่ซับซ้อนและมีการใช้งานที่ซับซ้อนมากขึ้น แต่หลักความคิดที่จะเข้าร่วมสองตารางกรองลงไปแถวที่ต้องมีการเปลี่ยนแปลง (แทรกปรับปรุงหรือลบ) และจากนั้นจะดำเนินการเปลี่ยนแปลงที่ร้องขอ รับข้อมูลตัวอย่างต่อไปนี้:
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 เข้ามาความพยายามที่ผิดพลาดอย่างหนึ่งในการปรับให้เหมาะสมเพื่อสัมผัสแถวที่จำเป็นเฉพาะในเป้าหมายคือ:
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
ประโยคถูกนำไปใช้เป็นส่วนหนึ่งของการเข้าร่วม ในกรณีนี้การเข้าร่วมเป็นการรวมภายนอกเต็มรูปแบบ (ดูรายการหนังสือออนไลน์สำหรับสาเหตุ) การใช้การตรวจสอบหมวดหมู่ 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
ไวยากรณ์ (และการดำเนินงานที่เข้าร่วมขึ้นอยู่กับคำสั่งที่ระบุไว้) เพียงแค่ทำให้มันยากขึ้นที่จะเห็นว่าเป็นเช่นนี้
คำแนะนำในการหนังสือออนไลน์ (ขยายตัวในการปฏิบัติงานเพิ่มประสิทธิภาพรายการ) มีคำแนะนำว่าจะให้ความหมายที่ถูกต้องจะแสดงโดยใช้MERGE
ไวยากรณ์โดยที่ผู้ใช้จำเป็นต้องเข้าใจทุกรายละเอียดการดำเนินการหรือการบัญชีสำหรับวิธีการที่เพิ่มประสิทธิภาพถูกต้องตามกฎหมายอาจจัดเรียง สิ่งต่าง ๆ สำหรับเหตุผลประสิทธิภาพการดำเนินการ
เอกสารประกอบมีสามวิธีที่เป็นไปได้ในการนำการกรองเริ่มต้นไปใช้:
การระบุเงื่อนไขการกรองใน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 แยกเป็นเรื่องง่ายขึ้น วิธีนี้สามารถทำได้ดีกว่า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;