178 votes

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

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

Comment faire un statut SQL qui DELETE ON CASCADE ?

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

Le tableau croisé dynamique "catégories_produits" crée une relation many-to-many entre les deux autres tableaux.

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

products
- id
- name
- price

categories_products
- categories_id
- products_id

417voto

Marc B Points 195501

Si vos suppressions en cascade détruisent un produit parce qu'il était membre d'une catégorie qui a été supprimée, c'est que vous avez mal configuré vos clés étrangères. Avec vos tables d'exemple, vous devriez avoir la configuration de table suivante :

CREATE TABLE categories (
    id int unsigned not null primary key,
    name VARCHAR(255) default null
);
CREATE TABLE products (
    id int unsigned not null primary key,
    name VARCHAR(255) default null
);

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
);

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 remontera pas plus haut dans l'arbre et ne supprimera pas la table parent produit/catégorie.

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' dans la table des catégories disparaît, ainsi que les deux entrées prod/cats : 'bottes rouges' et 'manteaux rouges'.

La suppression n'ira pas plus loin et ne supprimera pas les catégories "bottes" et "manteaux".

suivi des commentaires :

vous ne comprenez toujours pas comment les suppressions en cascade fonctionnent. Elles n'affectent que les tables dans lesquelles le "on delete cascade" est défini. 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 pas les enregistrements où 'category_id = blue', et il ne se rendra pas dans 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 n° 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 dont l'identifiant correspondant est 2. Comme nous n'avons défini la relation de clé étrangère qu'en products_categories vous vous retrouvez avec ce tableau 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 le products La cascade ne fonctionnera donc pas à cet endroit, et vous avez toujours des bottes et des moufles dans la liste. Il n'y a simplement plus de "bottes bleues" ni de "moufles bleues".

12voto

adigourdi Points 11

J'ai été troublé par la réponse à cette question, j'ai donc créé un cas de test dans MySQL, j'espère que cela vous 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

8voto

Hammerite Points 10786

Je pense (je ne suis pas certain) que les contraintes de clé étrangère ne feront pas précisément 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 voulez 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 que vous souhaitez en émettant la commande suivante CALL DeleteCategory(category_ID) (où category_ID est la catégorie à supprimer), et il se comportera comme vous le souhaitez. Mais n'émettez pas un DELETE FROM à moins que vous ne souhaitiez un comportement plus standard (c'est-à-dire supprimer uniquement dans la table de liaison et laisser la table de liaison dans la table de liaison). products tableau seul).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X