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_ITEMSpertama, lalu baris dari STOCK_ARTICLES.
- Gunakan
ON DELETE CASCADEuntuk 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 EXISTSklausa bersarang yang di dalamnya WHERE INmungkin 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_ITEMSuntuk memenuhi batasan bahwa tidak ada yang merujuk pada baris yang Anda hapus. Tambahkan saja ON DELETE CASCADEdefinisi 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 UPDATEatau 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 MERGEpernyataan 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 CASCADEbisa berbahaya , jadi gunakan dengan hati-hati.
Catatan samping: gunakan MERGE(atau UPDATE-dan- di INSERTmana MERGEtidak tersedia) ketika Anda membutuhkan UPSERToperasi, bukan DELETE -kemudian ganti dengan- INSERTuntuk menghindari jatuh ke dalam perangkap yang diletakkan oleh orang lain menggunakan ON DELETE CASCADE.
INVENTORY_ITEMSditambahkan di antara keduanyaDELETE.