2010-05-26 8 views
124

Je veux utiliser des clés étrangères pour garder l'intégrité et éviter les orphelins (j'utilise déjà innoDB).Contraintes de clé étrangère MySQL, cascade delete

Comment créer une instruction SQL SUPPRIMANT 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.

La table pivotante "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 
+0

Salut - vous pouvez modifier le titre de la question, il est à propos de la cascade supprime vraiment, pas tables pivotantes spécifiquement. – Paddyslacker

Répondre

333

Si votre cascade supprime un produit parce qu'il appartenait à une catégorie qui a été supprimée, vous n'avez pas correctement configuré vos clés étrangères. Compte tenu de vos tables d'exemple, vous devriez avoir la configuration du tableau suivant:

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 que les enregistrements associés à categories_products vont mourir à côté. La cascade ne se déplace pas plus loin dans l'arborescence et supprime la table de produits/catégories parente.

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 que l'entrée « rouge » dans le tableau des catégories meurt, ainsi que les deux entrées prod/chats: « bottes rouges » et les « tuniques rouges ».

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

commentaire suivi:

vous malentendu encore comment supprime le travail en cascade. Ils affectent uniquement les tables dans lesquelles "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 aucun enregistrement où 'category_id = blue', et il ne sera pas redirigé 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 se penchera sur toutes les tables qui ont une clé étrangère pointant à la table 'categories', et supprimez les enregistrements où l'ID correspondant est 2. Puisque nous avons seulement 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, donc la cascade ne fonctionnera pas là, donc vous avez toujours des bottes et des mitaines listées. Il n'y a plus de 'bottes bleues' et plus de 'mitaines bleues'.

+0

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 supprime pas les produits qui sont également liés à d'autres catégories. – Cudos

+25

C'est une très bonne réponse, très perspicace et merveilleusement illustrée. Merci d'avoir pris le temps de tout écrire. – scottb

+1

Lors de la création des tables, vous devez spécifier InnoDB ou un autre moteur MySQL capable d'effectuer des opérations 'CASCADE'. Sinon, la valeur par défaut de MySQL, MyISAM, sera utilisée et MyISAM ne supportera pas les opérations 'CASCADE'. Pour ce faire, il suffit d'ajouter 'ENGINE InnoDB' avant le dernier'; '. – Patrick

7

Je pense (je ne suis pas certain) que les clés étrangères ne feront pas exactement ce que vous voulez 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 clés étrangères 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 peut CONTRAINTE, bien sûr, apparaissent également 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 requête DELETE FROM normale, sauf si vous souhaitez un comportement plus standard (c'est-à-dire supprimer de la table de liaison uniquement et laisser la table products seule).

+0

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 supprime pas les produits qui sont également liés à d'autres catégories. – Cudos

+0

ok bien dans ce cas je pense que la réponse de Marc B fait ce que vous voulez. – Hammerite

9

Je suis confus par la réponse à cette question, donc je créé un test dans MySQL, espérons que cette 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