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

158

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

Comment créer une instruction SQL DELETE ON CASCADE?

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

Le tableau croisé dynamique "categories_products" 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
Cudos
la source
Salut - vous voudrez peut-être modifier le titre de la question, il s'agit vraiment de suppressions en cascade, pas spécifiquement de tableaux croisés dynamiques.
Paddyslacker

Réponses:

387

Si votre mise en cascade supprime l'arme nucléaire d'un produit parce qu'il faisait partie d'une catégorie qui a été tuée, c'est que vous avez mal configuré vos clés étrangères. Compte tenu de 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 OU une catégorie, et seuls les enregistrements associés dans categories_products mourront à côté. La cascade ne se déplacera pas plus haut dans l'arborescence et supprimera la table de produits / catégories 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», alors seule l'entrée «rouge» dans le tableau des catégories meurt, ainsi que les deux entrées prod / cats: «red boots» et «red coats».

La suppression ne se répercutera pas plus loin et ne supprimera pas les catégories «bottes» et «manteaux».

suivi des commentaires:

vous ne comprenez toujours pas comment fonctionnent les suppressions en cascade. Ils n'affectent que les tables dans lesquelles la "cascade à la suppression" est définie. Dans ce cas, la cascade est définie dans le tableau "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 il ne se déplacera pas vers la table "products", car il n'y a pas de clé étrangère 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
+------------+-------------+

Disons que vous supprimez la catégorie # 2 (bleu):

DELETE FROM categories WHERE (id = 2);

le SGBD examinera toutes les tables qui ont une clé étrangère pointant vers la table 'categories', et supprimera les enregistrements où l'ID correspondant est 2. Puisque nous n'avons défini la relation de clé étrangère que dans products_categories, vous vous retrouvez avec cette table une fois le la suppression est 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 le productstableau, donc la cascade ne fonctionnera pas là-bas, donc vous avez toujours des bottes et des mitaines répertoriées. Il n'y a plus de «bottes bleues» ni de «mitaines bleues».

Marc B
la source
Je pense que j'ai écrit ma question dans le mauvais sens. Si je supprime une catégorie, comment puis-je m'assurer qu'elle ne supprimera pas les produits qui sont également liés à d'autres catégories.
Cudos
36
C'est une réponse vraiment géniale, très claire et merveilleusement illustrée. Merci d'avoir pris le temps de tout écrire.
scottb
2
Lors de la création des tables, vous devez spécifier InnoDB ou un autre moteur MySQL capable d' CASCADEopérations. Sinon, la valeur par défaut de MySQL, MyISAM, sera utilisée et MyISAM ne prend pas en charge les CASCADEopérations. Pour ce faire, ajoutez simplement ENGINE InnoDBavant le dernier ;.
Patrick
11

J'ai été confus par la réponse à cette question, alors j'ai créé un cas de test dans MySQL, j'espère que cela aide

-- 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
la source
8

Je pense (je ne suis pas certain) que les contraintes de clé étrangère ne feront pas exactement ce que vous voulez étant donné la conception de votre table. La meilleure chose à faire est peut-être de définir une procédure stockée qui supprimera 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

La clause CONSTRAINT peut, bien entendu, également apparaître dans l'instruction CREATE TABLE.

Après avoir créé ces objets de schéma, vous pouvez supprimer une catégorie et obtenir le comportement souhaité en émettant CALL DeleteCategory(category_ID)(où category_ID est la catégorie à supprimer), et il se comportera comme vous le souhaitez. Mais n'émettez pas une DELETE FROMrequête normale , sauf si vous voulez un comportement plus standard (c'est-à-dire supprimer de la table de liaison uniquement et laisser la productstable seule).

Hammerite
la source
Je pense que j'ai écrit ma question dans le mauvais sens. Si je supprime une catégorie, comment puis-je m'assurer qu'elle ne supprimera pas les produits qui sont également liés à d'autres catégories.
Cudos
ok bien dans ce cas je pense que la réponse de Marc B fait ce que tu veux.
Hammerite
Bonjour @Hammerite, pouvez-vous me dire quelle est la signification de KEY pkey (product_id),la troisième CREATE TABLErequête dans la réponse acceptée?
Siraj Alam