The MERGE
pernyataan memiliki sintaks yang kompleks dan bahkan lebih kompleks pelaksanaan, tapi pada dasarnya idenya adalah untuk bergabung dengan dua tabel, filter ke baris yang perlu diubah (dimasukkan, diperbarui, atau dihapus), dan kemudian untuk melakukan perubahan yang diminta. Diberikan data sampel berikut:
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);
Target
╔════════════╦════════╗
║ CategoryId ║ ItemId ║
╠════════════╬════════╣
║ 1 ║ 1 ║
║ 2 ║ 1 ║
║ 1 ║ 2 ║
║ 1 ║ 3 ║
║ 2 ║ 3 ║
║ 3 ║ 5 ║
║ 4 ║ 5 ║
║ 3 ║ 6 ║
╚════════════╩════════╝
Sumber
╔════════════╦════════╗
║ CategoryId ║ ItemId ║
╠════════════╬════════╣
║ 2 ║ 2 ║
╚════════════╩════════╝
Hasil yang diinginkan adalah mengganti data dalam target dengan data dari sumber, tetapi hanya untuk CategoryId = 2
. Dengan mengikuti uraian yang MERGE
diberikan di atas, kita harus menulis kueri yang menggabungkan sumber dan target pada kunci saja, dan menyaring baris hanya dalam WHEN
klausa:
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
;
Ini memberikan hasil sebagai berikut:
╔═════════╦════════════╦════════╗
║ $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 ║
╚════════════╩════════╝
Rencana pelaksanaannya adalah:
Perhatikan bahwa kedua tabel dipindai sepenuhnya. Kami mungkin menganggap ini tidak efisien, karena hanya baris yang CategoryId = 2
akan terpengaruh dalam tabel target. Di sinilah peringatan di Books Online masuk. Salah satu upaya salah kaprah untuk mengoptimalkan hanya menyentuh baris yang diperlukan dalam target adalah:
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
;
Logika dalam ON
klausa diterapkan sebagai bagian dari gabungan. Dalam hal ini, join adalah join luar penuh (lihat entri Books Online ini untuk alasannya). Menerapkan tanda centang untuk kategori 2 pada baris target sebagai bagian dari gabungan luar akhirnya menghasilkan baris dengan nilai yang berbeda dihapus (karena mereka tidak cocok dengan sumber):
╔═════════╦════════════╦════════╗
║ $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 ║
╚════════════╩════════╝
Akar penyebabnya adalah alasan yang sama mengapa predikat berperilaku berbeda di ON
klausa join luar daripada yang mereka lakukan jika ditentukan dalam WHERE
klausa. The MERGE
sintaks (dan pelaksanaan bergabung tergantung pada klausul tertentu) hanya membuat lebih sulit untuk melihat bahwa ini adalah begitu.
The bimbingan dalam buku Online (diperluas dalam Kinerja Mengoptimalkan entry) menawarkan panduan yang akan menjamin semantik yang benar dinyatakan menggunakan MERGE
sintaks, tanpa pengguna harus harus memahami semua rincian pelaksanaan, atau akun untuk cara di mana optimizer mungkin sah mengatur ulang hal-hal untuk alasan efisiensi eksekusi.
Dokumentasi menawarkan tiga cara potensial untuk menerapkan penyaringan awal:
Menentukan kondisi pemfilteran dalam WHEN
klausa menjamin hasil yang benar, tetapi dapat berarti bahwa lebih banyak baris dibaca dan diproses dari tabel sumber dan target daripada yang sangat diperlukan (seperti yang terlihat pada contoh pertama).
Memutakhirkan melalui tampilan yang berisi kondisi pemfilteran juga menjamin hasil yang benar (karena baris yang diubah harus dapat diakses untuk pembaruan melalui tampilan) tetapi ini memang memerlukan tampilan khusus, dan yang mengikuti kondisi aneh untuk memperbarui tampilan.
Menggunakan ekspresi tabel umum memiliki risiko yang sama dengan menambahkan predikat ke ON
klausa, tetapi untuk alasan yang sedikit berbeda. Dalam banyak kasus itu akan aman, tetapi membutuhkan analisis ahli dari rencana eksekusi untuk mengkonfirmasi ini (dan pengujian praktis yang luas). Sebagai contoh:
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
;
Ini menghasilkan hasil yang benar (tidak diulang) dengan rencana yang lebih optimal:
Paket hanya membaca baris untuk kategori 2 dari tabel target. Ini mungkin pertimbangan kinerja yang penting jika tabel target besar, tetapi terlalu mudah untuk mendapatkan kesalahan ini menggunakan MERGE
sintaks.
Terkadang, lebih mudah untuk menulis MERGE
operasi DML yang terpisah. Pendekatan ini bahkan dapat berkinerja lebih baik daripada tunggal MERGE
, sebuah fakta yang sering mengejutkan orang.
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;