Das ist der springende Punkt bei Fremdschlüsseleinschränkungen: Sie verhindern, dass Sie Daten löschen, auf die an anderer Stelle verwiesen wird, um die referenzielle Integrität aufrechtzuerhalten.
Es gibt zwei Möglichkeiten:
- Löschen Sie zuerst die Zeilen von
INVENTORY_ITEMS
und dann die Zeilen von STOCK_ARTICLES
.
- Verwenden Sie
ON DELETE CASCADE
für die in der Schlüsseldefinition.
1: Löschen in der richtigen Reihenfolge
Der effizienteste Weg, dies zu tun, hängt von der Komplexität der Abfrage ab, die entscheidet, welche Zeilen gelöscht werden sollen. Ein allgemeines Muster könnte sein:
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
Dies ist in Ordnung für einfache Abfragen oder zum Löschen eines einzelnen Lagerartikels. Da Ihre Löschanweisung jedoch eine WHERE NOT EXISTS
Klausel enthält, die eine Verschachtelung enthält WHERE IN
, kann dies zu einem sehr ineffizienten Plan führen. Testen Sie daher mit einer realistischen Datensatzgröße und ordnen Sie die Abfrage bei Bedarf neu an.
Beachten Sie auch die Transaktionsanweisungen: Sie möchten sicherstellen, dass beide Löschvorgänge abgeschlossen sind oder keiner von beiden. Wenn der Vorgang bereits innerhalb einer Transaktion ausgeführt wird, müssen Sie dies offensichtlich ändern, um ihn an Ihren aktuellen Transaktions- und Fehlerbehandlungsprozess anzupassen.
2: Verwenden ON DELETE CASCADE
Wenn Sie Ihrem Fremdschlüssel die Kaskadenoption hinzufügen, erledigt SQL Server dies automatisch für Sie und entfernt Zeilen aus INVENTORY_ITEMS
, um die Einschränkung zu erfüllen, dass sich nichts auf die zu löschenden Zeilen beziehen sollte. Fügen Sie ON DELETE CASCADE
der FK-Definition einfach Folgendes hinzu :
ALTER TABLE <child_table> WITH CHECK
ADD CONSTRAINT <fk_name> FOREIGN KEY(<column(s)>)
REFERENCES <parent_table> (<column(s)>)
ON DELETE CASCADE
Ein Vorteil hierbei ist, dass das Löschen eine atomare Anweisung ist, die die Notwendigkeit verringert, sich um Transaktions- und Sperreinstellungen zu kümmern (obwohl dies wie üblich nicht zu 100% entfernt wird). Die Kaskade kann sogar über mehrere Ebenen Eltern / Kind / Enkel / ... betrieben werden, wenn nur ein Pfad zwischen Eltern und allen Nachkommen vorhanden ist (suchen Sie nach "mehreren Kaskadenpfaden", um Beispiele dafür zu finden, wo dies möglicherweise nicht funktioniert).
HINWEIS: Ich und viele andere halten kaskadierte Löschvorgänge für gefährlich. Wenn Sie diese Option verwenden, müssen Sie sie in Ihrem Datenbankdesign sorgfältig dokumentieren, damit Sie und andere Entwickler später nicht über die Gefahr stolpern . Aus diesem Grund vermeide ich das Kaskadieren von Löschungen, wo immer dies möglich ist.
Ein häufiges Problem bei kaskadierten Löschvorgängen besteht darin, dass jemand Daten aktualisiert, indem er Zeilen löscht und neu erstellt, anstatt UPDATE
oder zu verwenden MERGE
. Dies tritt häufig dort auf, wo "Aktualisieren der bereits vorhandenen Zeilen, Einfügen der nicht vorhandenen" (manchmal als UPSERT-Operation bezeichnet) erforderlich ist und Personen, die die MERGE
Anweisung nicht kennen, dies einfacher tun:
DELETE <all rows that match IDs in the new data>
INSERT <all rows from the new data>
als
-- 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
Das Problem hierbei ist, dass die delete-Anweisung in untergeordnete Zeilen kaskadiert und die insert-Anweisung diese nicht neu erstellt. Wenn Sie also die übergeordnete Tabelle aktualisieren, verlieren Sie versehentlich Daten aus den untergeordneten Tabellen.
Zusammenfassung
Ja, Sie müssen zuerst die untergeordneten Zeilen löschen.
Es gibt noch eine andere Option : ON DELETE CASCADE
.
Aber ON DELETE CASCADE
kann gefährlich sein , so mit Vorsicht verwendet werden .
Randnotiz: Verwenden Sie MERGE
(oder UPDATE
-und- INSERT
wo MERGE
nicht verfügbar), wenn Sie eine UPSERT
Operation benötigen, und ersetzen Sie sie nicht DELETE
durch, um INSERT
zu vermeiden, dass Sie in Fallen fallen, die von anderen Personen gelegt wurden ON DELETE CASCADE
.
INVENTORY_ITEMS
zwischen den beidenDELETE
s hinzugefügt werden .Ich bin auch auf dieses Problem gestoßen und konnte es beheben. Hier ist meine Situation:
In meinem Fall habe ich eine Datenbank, die zum Melden einer Analyse verwendet wird (MYTARGET_DB), die aus einem Quellsystem (MYSOURCE_DB) abgerufen wird. Einige der 'MYTARGET_DB'-Tabellen sind für dieses System eindeutig, und Daten werden dort erstellt und verwaltet. Die meisten Tabellen stammen aus 'MYSOURCE_DB' und es gibt einen Job, der die Daten aus 'MYSOURCE_DB' in 'MYTARGET_DB' löscht / einfügt.
Eine der Nachschlagetabellen [PRODUCT] stammt aus der SOURCE, und im TARGET ist eine Datentabelle [InventoryOutsourced] gespeichert. In den Tabellen ist eine referenzielle Integrität vorgesehen. Wenn ich also versuche, das Löschen / Einfügen auszuführen, wird diese Meldung angezeigt.
Die von mir erstellte Problemumgehung besteht darin, Daten aus [InventoryOutsourced] in die Tabellenvariable [@tempTable] einzufügen, Daten in [InventoryOutsourced] zu löschen, die Synchronisierungsjobs auszuführen und aus [@tempTable] in [InventoryOutsourced] einzufügen. Dadurch bleibt die Integrität erhalten, und die eindeutige Datenerfassung bleibt ebenfalls erhalten. Welches ist das Beste aus beiden Welten. Hoffe das hilft.
quelle
Ich habe noch nicht vollständig getestet, aber so etwas sollte funktionieren.
quelle