Questo è il punto centrale dei vincoli delle chiavi esterne: ti impediscono di eliminare i dati a cui si fa riferimento altrove per mantenere l'integrità referenziale.
Esistono due opzioni:
- Elimina prima le righe
INVENTORY_ITEMS
, quindi le righe da STOCK_ARTICLES
.
- Utilizzare
ON DELETE CASCADE
per la definizione chiave.
1: eliminazione nell'ordine corretto
Il modo più efficiente per farlo varia in base alla complessità della query che decide quali righe eliminare. Un modello generale potrebbe essere:
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
Questo va bene per semplici query o per eliminare un singolo articolo di scorta, ma dato la tua dichiarazione di eliminazione contiene una WHERE NOT EXISTS
clausola di annidamento che al suo interno WHERE IN
potrebbe produrre un piano molto inefficiente, quindi prova con una dimensione realistica del set di dati e riorganizza la query se necessario.
Nota anche le dichiarazioni di transazione: assicurati che entrambe le eliminazioni siano complete o nessuna delle due funzioni. Se l'operazione sta già avvenendo all'interno di una transazione, dovrai ovviamente modificarla in modo che corrisponda alla transazione corrente e al processo di gestione degli errori.
2: utilizzare ON DELETE CASCADE
Se aggiungi l'opzione a cascata alla tua chiave esterna, SQL Server lo farà automaticamente per te, rimuovendo le righe INVENTORY_ITEMS
per soddisfare il vincolo che nulla dovrebbe fare riferimento alle righe che stai eliminando. Basta aggiungere ON DELETE CASCADE
alla definizione FK in questo modo:
ALTER TABLE <child_table> WITH CHECK
ADD CONSTRAINT <fk_name> FOREIGN KEY(<column(s)>)
REFERENCES <parent_table> (<column(s)>)
ON DELETE CASCADE
Un vantaggio qui è che l'eliminazione è un'istruzione atomica che riduce (sebbene, come al solito, non la rimozione al 100%) la necessità di preoccuparsi delle impostazioni di transazione e blocco. La cascata può persino operare su più livelli genitore / figlio / nipote / ... se esiste un solo percorso tra genitore e tutti i discendenti (cercare "percorsi multipli in cascata" per esempi di dove potrebbe non funzionare).
NOTA: io, e molti altri, ritengo che le eliminazioni in cascata siano pericolose, quindi se si utilizza questa opzione fare molta attenzione a documentarla correttamente nella progettazione del database in modo che tu e altri sviluppatori non inciampiate sul pericolo in seguito . Evito le eliminazioni a cascata laddove possibile per questo motivo.
Un problema comune causato dalle eliminazioni in cascata è quando qualcuno aggiorna i dati rilasciando e ricreando le righe invece di usare UPDATE
o MERGE
. Questo è spesso visto dove è necessario "aggiornare le righe già esistenti, inserire quelle che non lo fanno" (a volte chiamato operazione UPSERT) e le persone che non sono consapevoli dell'istruzione MERGE
trovano più facile fare:
DELETE <all rows that match IDs in the new data>
INSERT <all rows from the new data>
di
-- 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
Il problema qui è che l'istruzione delete passerà in cascata alle righe figlio e l'istruzione insert non le ricrea, quindi durante l'aggiornamento della tabella padre si perderanno accidentalmente i dati dalle tabelle figlio.
Sommario
Sì, devi prima eliminare le righe figlio.
C'è un'altra opzione: ON DELETE CASCADE
.
Ma ON DELETE CASCADE
può essere pericoloso , quindi usalo con cura.
Nota a margine: usare MERGE
(o UPDATE
-e-- INSERT
dove MERGE
non è disponibile) quando è necessaria UPSERT
un'operazione, non DELETE
-poi-sostituire-con- INSERT
per evitare di cadere in trappole posate da altre persone che usano ON DELETE CASCADE
.
INVENTORY_ITEMS
aggiunti tra i dueDELETE
.