У цьому і полягає вся суть закордонних ключових обмежень: вони перешкоджають видаленню даних, які посилаються в інше місце, щоб зберегти референтну цілісність.
Є два варіанти:
- Видаліть ряди
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що додаються між двомаDELETEs.