2010-05-26 164 views
124

我想使用外键保持完整性并避免孤儿(我已经使用innoDB)。MySQL外键约束,级联删除

如何创建DELETE ON CASCADE的SQL语句?

如果我删除一个类别,那么我如何确保它不会删除也与其他类别相关的产品。

数据透视表“categories_products”在两个其他表之间创建了多对多关系。

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

products 
- id 
- name 
- price 

categories_products 
- categories_id 
- products_id 
+0

嗨 - 你可能要修改的问题的标题,它是关于级联删除真的,不特别是透视表。 – Paddyslacker 2010-05-26 16:53:15

回答

333

如果您的级联删除了一个产品,因为它是被杀的类别的成员,那么您已经设置了不正确的外键。鉴于您的示例表,你应该有如下表设置:

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; 

这样,你就可以删除产品或类别,只有在categories_products相关记录将一起死。级联不会继续向上移动,并删除父级产品/类别表。

例如

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

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

如果删除了“红”类别,那么只有“红色”的类别表项死了,还有两个条目PROD /猫:“红色靴子”和“红色大衣”。

删除将不会级联任何更远,不会取出“靴子”和“大衣”类别。

评论随访:

你还误解如何级联删除工作。它们只影响定义“on delete cascade”的表格。在这种情况下,级联设置在“categories_products”表中。如果您删除“红色”类别,那么类别_产品中将级联删除的唯一记录是category_id = red。它不会触及'category_id = blue'的任何记录,并且它不会前移到“产品”表,因为该表中没有定义外键。

这里有一个更具体的例子:

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

的DBMS会看它必须在一个外键指向的所有表'categories'表,并且删除匹配的id为2的记录。由于我们只定义了products_categories中的外键关系,所以在删除完成后,您将以此表结束:

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

products表中没有定义外键,所以级联不会在那里工作,所以您仍然列出了靴子和手套。没有'蓝色靴子',也没有'蓝色手套'了。

+0

我想我写错了我的问题。如果我删除一个类别,那么我如何确保它不会删除也与其他类别相关的产品。 – Cudos 2010-05-27 07:26:44

+25

这是一个非常好的,非常明显的,并且很好地说明了答案。感谢您抽出时间全部写出来。 – scottb 2013-04-19 17:38:32

+1

创建表时,您需要指定InnoDB或另一个能够执行“CASCADE”操作的MySQL引擎。否则,将使用MySQL默认值MyISAM,MyISAM不支持“CASCADE”操作。为此,只需在最后一个';'之前添加'ENGINE InnoDB'。 – Patrick 2014-04-28 00:46:43

7

我觉得(我不能肯定)外键约束不会做正是你想给你的表的设计。也许最好的做法是定义一个存储过程,以您想要的方式删除一个类别,然后在您想要删除某个类别时调用该过程。

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 

约束条款可以,当然,也出现在CREATE TABLE语句。

创建了这些模式对象之后,您可以通过发出CALL DeleteCategory(category_ID)(其中category_ID是要删除的类别)来删除某个类别并获取所需的行为,并且该行为将以您想要的方式运行。但不要发出正常的DELETE FROM查询,除非您想要更多的标准行为(即仅从链接表中删除,并且仅保留products表)。

+0

我想我以错误的方式写了我的问题。如果我删除一个类别,那么我如何确保它不会删除也与其他类别相关的产品。 – Cudos 2010-05-27 07:25:19

+0

没问题,在这种情况下,我认为马克B的答案是做你想做的。 – Hammerite 2010-05-27 18:29:13

9

我被这个问题的答案感到困惑,所以我创建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