Ограничения внешнего ключа MySQL, каскадное удаление

158

Я хочу использовать внешние ключи, чтобы сохранить целостность и избежать сирот (я уже использую innoDB).

Как сделать SQL-оператор, который удаляет CASCADE?

Если я удаляю категорию, то как мне убедиться, что она не удалит товары, которые также относятся к другим категориям.

Сводная таблица «category_products» создает отношение «многие ко многим» между двумя другими таблицами.

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

products
- id
- name
- price

categories_products
- categories_id
- products_id
Cudos
источник
Привет - вы можете изменить заголовок вопроса, он касается каскадного удаления, а не сводных таблиц.
Paddyslacker

Ответы:

387

Если ваш каскад удаляет ядерный продукт, потому что он был членом убитой категории, то вы неправильно настроили свои внешние ключи. Учитывая ваши примеры таблиц, вы должны иметь следующую настройку таблицы:

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;

Таким образом, вы можете удалить продукт ИЛИ категорию, и только связанные записи в category_products умрут вместе с ним. Каскад не пройдет дальше по дереву и не удалит родительскую таблицу товаров / категорий.

например

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

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

Если вы удалите «красную» категорию, то умирает только «красная» запись в таблице категорий, а также две записи prod / cats: «красные сапоги» и «красные пальто».

Удаление не будет касаться дальше и не удалит категории «ботинки» и «пальто».

продолжение комментария:

Вы все еще не понимаете, как работает каскадное удаление. Они влияют только на таблицы, в которых определен «каскад удаления». В этом случае каскад устанавливается в таблице «category_products». Если вы удалите «красную» категорию, единственными записями, которые будут каскадно удаляться в category_products, являются те, где category_id = red. Он не будет касаться записей, где «category_id = blue», и не будет перемещаться дальше к таблице «products», потому что в этой таблице не определен внешний ключ.

Вот более конкретный пример:

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таблице не определен внешний ключ , поэтому каскад не будет работать там, поэтому у вас все еще есть ботинки и рукавицы. Просто больше нет «синих сапог» и «синих варежек».

Марк Б
источник
Я думаю, что написал свой вопрос неправильно. Если я удаляю категорию, то как мне убедиться, что она не удалит товары, которые также относятся к другим категориям.
Слава
36
Это действительно отличный, очень заметный и прекрасно иллюстрированный ответ. Спасибо, что нашли время, чтобы написать все это.
Скоттб
2
При создании таблиц вам нужно указать InnoDB или другой движок MySQL, способный CASCADEработать. В противном случае будет использоваться MyISAM по умолчанию, 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
Abderrahim
источник
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)(где category_ID - категория, которую нужно удалить), и она будет вести себя так, как вы хотите. Но не выполняйте нормальный DELETE FROMзапрос, если только вы не хотите более стандартного поведения (т.е. удалите только из таблицы ссылок и оставьте productsтаблицу в покое).

Hammerite
источник
Я думаю, что написал свой вопрос неправильно. Если я удаляю категорию, то как мне убедиться, что она не удалит товары, которые также относятся к другим категориям.
Слава
хорошо, в таком случае, я думаю, что ответ Марка Б. делает то, что вы хотите.
Hammerite
Здравствуйте @Hammerite, подскажите, пожалуйста, что означает KEY pkey (product_id),третий CREATE TABLEзапрос в принятом ответе?
Сирадж Алам