В этом весь смысл ограничений внешнего ключа: они мешают вам удалять данные, на которые ссылаются в других местах, чтобы поддерживать ссылочную целостность.
Есть два варианта:
INVENTORY_ITEMS
Сначала удалите строки , затем строки из STOCK_ARTICLES
.
- Используйте
ON DELETE CASCADE
для определения ключа.
1: удаление в правильном порядке
Наиболее эффективный способ сделать это зависит от сложности запроса, который решает, какие строки удалить. Общий шаблон может быть:
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
Это хорошо для простых запросов или для удаления отдельного элемента запаса, но, учитывая, что ваш оператор удаления содержит WHERE NOT EXISTS
вложенное предложение, которое WHERE IN
может привести к очень неэффективному плану, так что тестируйте с реалистичным размером набора данных и перегруппируйте запрос при необходимости.
Также обратите внимание на операторы транзакций: вы хотите убедиться, что оба удаления завершены, или ни одно из них не выполнено. Если операция уже происходит внутри транзакции, вам, очевидно, потребуется изменить ее, чтобы она соответствовала вашей текущей транзакции и процессу обработки ошибок.
2: использовать ON DELETE CASCADE
Если вы добавите опцию cascade к своему внешнему ключу, SQL Server автоматически сделает это за вас, удалив строки из, INVENTORY_ITEMS
чтобы удовлетворить ограничение, что ничто не должно ссылаться на удаляемые вами строки. Просто добавьте ON DELETE CASCADE
к определению FK вот так:
ALTER TABLE <child_table> WITH CHECK
ADD CONSTRAINT <fk_name> FOREIGN KEY(<column(s)>)
REFERENCES <parent_table> (<column(s)>)
ON DELETE CASCADE
Преимущество здесь состоит в том, что удаление - это одно атомарное утверждение, уменьшающее (хотя, как обычно, не 100% удаление) необходимость беспокоиться о настройках транзакции и блокировки. Каскад может даже работать на нескольких уровнях parent / child / grand-child / ..., если существует только один путь между parent и всеми потомками (ищите «несколько путей каскада» для примеров, где это может не сработать).
ПРИМЕЧАНИЕ. Я и многие другие полагаем, что каскадное удаление опасно, поэтому, если вы используете эту опцию, будьте очень осторожны, чтобы правильно документировать ее в своей структуре базы данных, чтобы вы и другие разработчики не опрокинули опасность позже . По этой причине я избегаю каскадного удаления, где это возможно.
Распространенная проблема, связанная с каскадным удалением, - это когда кто-то обновляет данные, удаляя и воссоздавая строки вместо использования UPDATE
или MERGE
. Это часто встречается, когда необходимо «обновить уже существующие строки, вставить те, которые не существуют» (иногда это называется операцией UPSERT), и люди, не знающие этого MERGE
утверждения, считают, что это легче сделать:
DELETE <all rows that match IDs in the new data>
INSERT <all rows from the new data>
чем
-- 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
Проблема в том, что оператор delete будет каскадно добавляться к дочерним строкам, а оператор вставки не будет их воссоздавать, поэтому при обновлении родительской таблицы вы случайно потеряете данные из дочерних таблиц.
Резюме
Да, вы должны сначала удалить дочерние строки.
Существует еще один вариант: ON DELETE CASCADE
.
Но ON DELETE CASCADE
может быть опасно , поэтому используйте с осторожностью.
Примечание: используйте MERGE
(или - и UPDATE
там, INSERT
где MERGE
это невозможно), когда вам нужна UPSERT
операция, а не « DELETE
потом заменяйте», INSERT
чтобы избежать попадания в ловушки, заложенные другими людьми ON DELETE CASCADE
.
INVENTORY_ITEMS
, добавляемые между двумяDELETE
s.