Ограничения внешнего ключа MySQL, каскадное удаление - PullRequest
144 голосов
/ 26 мая 2010

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

Как создать SQL-оператор, который УДАЛИТ НА КАСКАДЕ?

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

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

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

products
- id
- name
- price

categories_products
- categories_id
- products_id

Ответы [ 3 ]

361 голосов
/ 27 мая 2010

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

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: «красные сапоги» и «красные пальто».

Удаление не будет каскадироваться дальше и не удалит категории 'boots' и 'coat'.

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

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

11 голосов
/ 24 апреля 2014

Меня смутил ответ на этот вопрос, поэтому я создал тестовый пример в 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 голосов
/ 27 мая 2010

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

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 в покое).

...