web-dev-qa-db-fra.com

Contraintes de clé étrangère MySQL, suppression en cascade

Je souhaite utiliser des clés étrangères pour préserver l'intégrité et éviter les orphelins (j'utilise déjà innoDB).

Comment créer un état SQL qui supprime DELETE ON CASCADE?

Si je supprime une catégorie, comment puis-je m'assurer qu'elle ne supprime pas les produits qui sont également liés à d'autres catégories.

Le tableau croisé dynamique "catégories_produits" crée une relation plusieurs à plusieurs entre les deux autres tables.

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

products
- id
- name
- price

categories_products
- categories_id
- products_id
150
Cudos

Si votre cascade supprime un produit parce qu'il appartenait à une catégorie tuée, alors vous avez mal configuré vos clés étrangères. Étant donné vos exemples de tables, vous devriez avoir la configuration de table suivante:

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;

De cette façon, vous pouvez supprimer un produit OR une catégorie et seuls les enregistrements associés dans les catégories_produits mourront en même temps. La cascade n'ira pas plus loin dans l'arborescence et ne supprimera pas la table produit/catégorie parent.

par exemple.

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

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

Si vous supprimez la catégorie "rouge", seule l'entrée "rouge" du tableau des catégories est supprimée, ainsi que les deux entrées prod/cats: "bottes rouges" et "manteaux rouges".

La suppression ne cascade pas plus loin et ne supprime pas les catégories "bottes" et "manteaux".

suivi des commentaires:

vous ne comprenez toujours pas comment fonctionne la suppression en cascade. Ils n'affectent que les tables dans lesquelles la "cascade en suppression" est définie. Dans ce cas, la cascade est définie dans la table "categories_products". Si vous supprimez la catégorie 'rouge', les seuls enregistrements qui seront supprimés en cascade dans categories_products sont ceux où category_id = red. Il ne touchera aucun enregistrement où "category_id = blue" et ne sera pas transmis à la table "products", car aucune clé étrangère n'est définie dans cette table.

Voici un exemple plus concret:

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
+------------+-------------+

Supposons que vous supprimiez la catégorie 2 (en bleu):

DELETE FROM categories WHERE (id = 2);

le SGBD examinera toutes les tables ayant une clé étrangère pointant sur la table 'catégories' et supprimera les enregistrements dont l'identifiant correspond à 2. Puisque nous avons uniquement défini la relation de clé étrangère dans products_categories, vous vous retrouvez avec cette table une fois la suppression terminée:

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

Il n'y a pas de clé étrangère définie dans la table products, de sorte que la cascade ne fonctionnera pas là-bas, vous aurez donc toujours des bottes et des mitaines dans la liste. Il n'y a plus de "bottes bleues" ni de "mitaines bleues".

368
Marc B

La réponse à cette question m'a confondu. J'ai donc créé un scénario de test dans MySQL. J'espère que cela aidera

-- 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
11
Abderrahim

Je pense (je ne suis pas certain) que les contraintes de clé étrangère ne feront pas exactement ce que vous voulez, vu la conception de votre table. La meilleure chose à faire est peut-être de définir une procédure stockée qui supprime une catégorie comme vous le souhaitez, puis d'appeler cette procédure chaque fois que vous souhaitez supprimer une catégorie.

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

Vous devez également ajouter les contraintes de clé étrangère suivantes à la table de liaison:

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

Bien entendu, la clause CONSTRAINT peut également apparaître dans l’instruction CREATE TABLE.

Une fois ces objets de schéma créés, vous pouvez supprimer une catégorie et obtenir le comportement souhaité en lançant CALL DeleteCategory(category_ID) (où category_ID correspond à la catégorie à supprimer) et il se comportera comme vous le souhaitez. Mais n'émettez pas de requête normale DELETE FROM, à moins que vous ne souhaitiez un comportement plus standard (c'est-à-dire supprimer uniquement de la table de liaison et laisser la table products seule).

8
Hammerite