Itulah inti dari batasan kunci asing: mereka menghentikan Anda menghapus data yang dirujuk ke tempat lain untuk menjaga integritas referensial.
Ada dua opsi:
- Hapus baris dari
INVENTORY_ITEMS
pertama, lalu baris dari STOCK_ARTICLES
.
- Gunakan
ON DELETE CASCADE
untuk definisi kunci.
1: Menghapus Secara Benar
Cara paling efisien untuk melakukan ini bervariasi tergantung pada kompleksitas kueri yang menentukan baris mana yang akan dihapus. Pola umum mungkin:
BEGIN TRANSACTION
SET XACT_ABORT ON
DELETE INVENTORY_ITEMS WHERE STOCK_ARTICLE IN (<select statement that returns stock_article.id for the rows you are about to delete>)
DELETE STOCK_ARTICLES WHERE <the rest of your current delete statement>
COMMIT TRANSACTION
Ini bagus untuk permintaan sederhana atau untuk menghapus satu stok barang, tetapi mengingat pernyataan penghapusan Anda berisi WHERE NOT EXISTS
klausa bersarang yang di dalamnya WHERE IN
mungkin menghasilkan rencana yang sangat tidak efisien, maka uji dengan ukuran set data yang realistis dan atur ulang kueri jika diperlukan.
Perhatikan juga laporan transaksi: Anda ingin memastikan semua penghapusan selesai atau tidak ada yang dilakukan. Jika operasi sudah terjadi dalam transaksi, Anda jelas perlu mengubahnya untuk mencocokkan transaksi Anda saat ini dan proses penanganan kesalahan.
2: Gunakan ON DELETE CASCADE
Jika Anda menambahkan opsi kaskade ke kunci asing Anda maka SQL Server akan secara otomatis melakukan ini untuk Anda, menghapus baris dari INVENTORY_ITEMS
untuk memenuhi batasan bahwa tidak ada yang merujuk pada baris yang Anda hapus. Tambahkan saja ON DELETE CASCADE
definisi FK seperti:
ALTER TABLE <child_table> WITH CHECK
ADD CONSTRAINT <fk_name> FOREIGN KEY(<column(s)>)
REFERENCES <parent_table> (<column(s)>)
ON DELETE CASCADE
Keuntungan di sini adalah bahwa penghapusan adalah satu pernyataan atom yang mengurangi (meskipun, seperti biasa, tidak menghilangkan 100%) kebutuhan untuk khawatir tentang pengaturan transaksi dan kunci. Kaskade bahkan dapat beroperasi di beberapa level induk / anak / cucu / ... jika hanya ada satu jalur antara induk dan semua keturunan (cari "beberapa jalur kaskade" untuk contoh di mana ini mungkin tidak berfungsi).
CATATAN: Saya, dan banyak orang lain, menganggap penghapusan bertingkat menjadi berbahaya sehingga jika Anda menggunakan opsi ini, berhati-hatilah untuk mendokumentasikannya dengan benar dalam desain basis data Anda sehingga Anda dan pengembang lainnya tidak tersandung bahaya nantinya . Saya menghindari penghapusan cascading sedapat mungkin karena alasan ini.
Masalah umum yang disebabkan oleh penghapusan bertingkat adalah ketika seseorang memperbarui data dengan menjatuhkan dan membuat ulang baris alih-alih menggunakan UPDATE
atau MERGE
. Ini sering terlihat di mana "perbarui baris yang sudah ada, masukkan yang tidak" (kadang-kadang disebut operasi UPSERT) diperlukan dan orang yang tidak mengetahui MERGE
pernyataan tersebut merasa lebih mudah untuk melakukan:
DELETE <all rows that match IDs in the new data>
INSERT <all rows from the new data>
dari
-- updates
UPDATE target
SET <col1> = source.<col1>
, <col2> = source.<col2>
...
, <colN> = source.<colN>
FROM <target_table> AS target JOIN <source_table_or_view_or_statement> AS source ON source.ID = target.ID
-- inserts
INSERT <target_table>
SELECT *
FROM <source_table_or_other> AS source
LEFT OUTER JOIN
<target_table> AS target
ON target.ID = source.ID
WHERE target.ID IS NULL
Masalahnya di sini adalah bahwa pernyataan penghapusan akan mengalir ke baris anak, dan pernyataan insert tidak akan membuatnya kembali, jadi saat memperbarui tabel induk Anda secara tidak sengaja kehilangan data dari tabel anak.
Ringkasan
Ya, Anda harus menghapus baris anak terlebih dahulu.
Ada pilihan lain: ON DELETE CASCADE
.
Tapi ON DELETE CASCADE
bisa berbahaya , jadi gunakan dengan hati-hati.
Catatan samping: gunakan MERGE
(atau UPDATE
-dan- di INSERT
mana MERGE
tidak tersedia) ketika Anda membutuhkan UPSERT
operasi, bukan DELETE
-kemudian ganti dengan- INSERT
untuk menghindari jatuh ke dalam perangkap yang diletakkan oleh orang lain menggunakan ON DELETE CASCADE
.
INVENTORY_ITEMS
ditambahkan di antara keduanyaDELETE
.