У цьому і полягає вся суть закордонних ключових обмежень: вони перешкоджають видаленню даних, які посилаються в інше місце, щоб зберегти референтну цілісність.
Є два варіанти:
- Видаліть ряди
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
Якщо ви додасте параметр каскаду до свого зовнішнього ключа, тоді 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% видалення) необхідності турбуватися про налаштування транзакцій та блокування. Каскад може працювати навіть на кількох рівнях батьків / дитини / онука / дитини / ..., якщо між батьком та всіма нащадками є лише один шлях (шукайте "декілька каскадних шляхів" для прикладів, коли це може не працювати).
ПРИМІТКА: Я та багато інших вважаю каскадні делети небезпечними, тому якщо ви використовуєте цю опцію, будьте дуже обережні, щоб правильно її задокументувати у вашій базі даних, щоб ви та інші розробники не подолали небезпеку пізніше . Я уникаю каскадних делетів, де це можливо, з цієї причини.
Поширена проблема, спричинена каскадними видаленнями, коли хтось оновлює дані, скидаючи та відтворюючи рядки замість використання 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.