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

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

Как сделать SQL statment, который удаляет на каскаде?

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

сводная таблица "categories_products" создает отношение "многие ко многим" между двумя другими таблицами.

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

products
- id
- name
- price

categories_products
- categories_id
- products_id

3 ответов


если ваш каскад удаляет 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;

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

например

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

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

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

удаление не будет каскадировать дальше и не будет вынимать категории "сапоги" и "пальто".

комментарий followup:

вы все еще не понимаете, как каскадные удаления работают. Они влияют только на таблицы, в которых " on delete cascade" определяемый. В этом случае каскад задается в таблице "categories_products". Если вы удалите "красную" категорию, единственными записями, которые будут каскадно удалять в categories_products, являются те, где 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 таблица, поэтому каскад не будет работать там, поэтому у вас все еще есть сапоги и варежки. Там просто нет "синих сапог" и никаких "синих варежек".


Я запутался в ответе на этот вопрос, поэтому я создал тестовый случай в 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

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

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

предложение ограничения может, конечно, также появиться в CREATE Заявление таблицы.

создав эти объекты схемы, вы можете удалить категорию и получить поведение, которое вы хотите, выдав CALL DeleteCategory(category_ID) (где category_ID - категория, которую нужно удалить), и она будет вести себя так, как вы хотите. Но не выдавать нормальный DELETE FROM запрос, если вы не хотите более стандартного поведения (т. е. удалить только из таблицы ссылок и оставить таблица).