Обмеження зовнішніх ключів MySQL, каскадне видалення


158

Я хочу використовувати зовнішні ключі, щоб зберегти цілісність та уникати дітей-сиріт (я вже використовую innoDB).

Як мені зробити SQL-статтю, ЩО ВИДАЛИТИ НА КАСКАД?

Якщо я видаляю категорію, то як я переконуюсь, що вона не видалить продукти, які також відносяться до інших категорій.

Зведена таблиця "Categories_products" створює взаємозв'язок між багатьма іншими таблицями.

categories
- id (INT)
- name (VARCHAR 255)

products
- id
- name
- price

categories_products
- categories_id
- products_id

Привіт - ви, можливо, захочете змінити назву питання, мова йде про дійсно видалення каскаду, а не конкретно зведених таблиць.
Paddyslacker

Відповіді:


387

Якщо ваш каскад видаляє продукт nuke, тому що він входив до категорії вбитої категорії, значить, ви встановили свої зовнішні ключі неправильно. З огляду на ваші приклади таблиць, ви повинні мати таку настройку таблиці:

CREATE TABLE categories (
    id int unsigned not null primary key,
    name VARCHAR(255) default null
)Engine=InnoDB;

CREATE TABLE products (
    id int unsigned not null primary key,
    name VARCHAR(255) default null
)Engine=InnoDB;

CREATE TABLE categories_products (
    category_id int unsigned not null,
    product_id int unsigned not null,
    PRIMARY KEY (category_id, product_id),
    KEY pkey (product_id),
    FOREIGN KEY (category_id) REFERENCES categories (id)
       ON DELETE CASCADE
       ON UPDATE CASCADE,
    FOREIGN KEY (product_id) REFERENCES products (id)
       ON DELETE CASCADE
       ON UPDATE CASCADE
)Engine=InnoDB;

Таким чином, ви можете видалити продукт АБО категорію, і поряд із цим загинуть лише пов’язані записи в категорії_продукти. Каскад не переміститься далі по дереву та видалить таблицю батьківського продукту / категорії.

напр

products: boots, mittens, hats, coats
categories: red, green, blue, white, black

prod/cats: red boots, green mittens, red coats, black hats

Якщо ви видалите категорію "червоний", вмирає лише запис "червоний" у таблиці категорій, а також два записи prod / cats: "червоні чоботи" та "червоні шуби".

Видалення не буде наближатись далі і не виведе категорії "чоботи" та "пальто".

подальший коментар:

ви все ще не розумієте, як працюють каскадні видалення. Вони впливають лише на таблиці, в яких визначено "каскад видалення". У цьому випадку каскад встановлюється в таблиці "категорії_продукти". Якщо ви видалите категорію "червоний", лише записи, які будуть каскадно видаляти, у категоріях_продуктів - це ті, де category_id = red. Він не торкнеться жодного запису, де 'category_id = blue', і він не переміщуватиметься до таблиці "товари", оскільки в цій таблиці немає жодного зовнішнього ключа.

Ось більш конкретний приклад:

categories:     products:
+----+------+   +----+---------+
| id | name |   | id | name    |
+----+------+   +----+---------+
| 1  | red  |   | 1  | mittens |
| 2  | blue |   | 2  | boots   |
+---++------+   +----+---------+

products_categories:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1          | 1           | // red mittens
| 1          | 2           | // blue mittens
| 2          | 1           | // red boots
| 2          | 2           | // blue boots
+------------+-------------+

Скажімо, ви видалите категорію №2 (синій):

DELETE FROM categories WHERE (id = 2);

СУБД перегляне всі таблиці, які мають зовнішній ключ, що вказує на таблицю "категорії", і видалить записи, де відповідає ідентифікаційний номер 2. Оскільки ми визначали лише взаємозв'язок із зовнішнім ключем products_categories, ви закінчуєте цю таблицю, як тільки видалити завершує:

+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1          | 1           | // red mittens
| 2          | 1           | // red boots
+------------+-------------+

У productsтаблиці немає жодного закордонного ключа , тому каскад там не працюватиме, тому у вас все ще є перелічені чоботи та рукавиці. Просто немає «блакитних черевиків» і «синіх рукавиць» більше.


Я думаю, що я написав своє запитання неправильно. Якщо я видаляю категорію, то як я переконуюсь, що вона не видалить продукти, які також відносяться до інших категорій.
Cudos

36
Це дійсно чудова, дуже прониклива і чудово ілюстрована відповідь. Дякуємо, що знайшли час, щоб усе це виписати.
scottb

2
Створюючи таблиці, вам потрібно вказати InnoDB або інший движок MySQL, здатний CASCADEпрацювати. В іншому випадку буде використовуватися MySQL за замовчуванням MyISAM, і MyISAM не підтримує CASCADEоперації. Для цього просто додайте ENGINE InnoDBперед останнім ;.
Патрік

11

Я розгубився у відповіді на це питання, тому я створив тестовий випадок у MySQL, сподіваюся, це допоможе

-- Schema
CREATE TABLE T1 (
    `ID` int not null auto_increment,
    `Label` varchar(50),
    primary key (`ID`)
);

CREATE TABLE T2 (
    `ID` int not null auto_increment,
    `Label` varchar(50),
    primary key (`ID`)
);

CREATE TABLE TT (
    `IDT1` int not null,
    `IDT2` int not null,
    primary key (`IDT1`,`IDT2`)
);

ALTER TABLE `TT`
    ADD CONSTRAINT `fk_tt_t1` FOREIGN KEY (`IDT1`) REFERENCES `T1`(`ID`) ON DELETE CASCADE,
    ADD CONSTRAINT `fk_tt_t2` FOREIGN KEY (`IDT2`) REFERENCES `T2`(`ID`) ON DELETE CASCADE;

-- Data
INSERT INTO `T1` (`Label`) VALUES ('T1V1'),('T1V2'),('T1V3'),('T1V4');
INSERT INTO `T2` (`Label`) VALUES ('T2V1'),('T2V2'),('T2V3'),('T2V4');
INSERT INTO `TT` (`IDT1`,`IDT2`) VALUES
(1,1),(1,2),(1,3),(1,4),
(2,1),(2,2),(2,3),(2,4),
(3,1),(3,2),(3,3),(3,4),
(4,1),(4,2),(4,3),(4,4);

-- Delete
DELETE FROM `T2` WHERE `ID`=4; -- Delete one field, all the associated fields on tt, will be deleted, no change in T1
TRUNCATE `T2`; -- Can't truncate a table with a referenced field
DELETE FROM `T2`; -- This will do the job, delete all fields from T2, and all associations from TT, no change in T1

8

Я думаю (я не впевнений), що обмеження зовнішніх ключів не будуть робити саме те, що ви хочете, враховуючи дизайн вашого столу. Мабуть, найкраще зробити це визначити збережену процедуру, яка видалить категорію так, як вам потрібно, а потім викликати цю процедуру щоразу, коли ви хочете видалити категорію.

CREATE PROCEDURE `DeleteCategory` (IN category_ID INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN

DELETE FROM
    `products`
WHERE
    `id` IN (
        SELECT `products_id`
        FROM `categories_products`
        WHERE `categories_id` = category_ID
    )
;

DELETE FROM `categories`
WHERE `id` = category_ID;

END

Вам також потрібно додати до обмежувальної таблиці наступні обмеження іноземних ключів:

ALTER TABLE `categories_products` ADD
    CONSTRAINT `Constr_categoriesproducts_categories_fk`
    FOREIGN KEY `categories_fk` (`categories_id`) REFERENCES `categories` (`id`)
    ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `Constr_categoriesproducts_products_fk`
    FOREIGN KEY `products_fk` (`products_id`) REFERENCES `products` (`id`)
    ON DELETE CASCADE ON UPDATE CASCADE

Становище CONSTRAINT може, звичайно, також відображатися у операторі CREATE TABLE.

Створивши ці об’єкти схеми, ви можете видалити категорію та отримати потрібну поведінку, видавши CALL DeleteCategory(category_ID)(де категорія_ID - категорія, яку потрібно видалити), і вона буде вести себе як ви хочете. Але не надсилайте звичайний DELETE FROMзапит, якщо ви не хочете більш стандартної поведінки (наприклад, видаліть тільки зі зв’язкової таблиці та не залиште productsтаблицю в спокої).


Я думаю, що я написав своє запитання неправильно. Якщо я видаляю категорію, то як я переконуюсь, що вона не видалить продукти, які також відносяться до інших категорій.
Cudos

гаразд у цьому випадку я думаю, що відповідь Марка Б робить те, що ти хочеш.
Хаммерит

Привіт @Hammerite, скажіть, будь ласка, яке значення має KEY pkey (product_id),третій CREATE TABLEзапит у прийнятій відповіді?
Siraj Alam
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.