简体   繁体   English

MySQL外键约束,级联删除

[英]MySQL foreign key constraints, cascade delete

I want to use foreign keys to keep the integrity and avoid orphans (I already use innoDB). 我想使用外键来保持完整性并避免使用孤立键(我已经使用过innoDB)。

How do I make a SQL statment that DELETE ON CASCADE? 如何创建在CASCADE上删除的SQL语句?

If I delete a category then how do I make sure that it would not delete products that also are related to other categories. 如果我删除一个类别,那么如何确保它不会删除也与其他类别相关的产品。

The pivot table "categories_products" creates a many-to-many relationship between the two other tables. 数据透视表“ categories_products”在其他两个表之间创建多对多关系。

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

products
- id
- name
- price

categories_products
- categories_id
- products_id

If your cascading deletes nuke a product because it was a member of a category that was killed, then you've set up your foreign keys improperly. 如果您的级联删除某个产品是因为该产品属于被杀类别的成员,那么它会删除该产品,那么您的外键设置不正确。 Given your example tables, you should have the following table setup: 给定示例表,您应该具有以下表设置:

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;

This way, you can delete a product OR a category, and only the associated records in categories_products will die alongside. 这样,您可以删除产品或类别,只有category_products中的关联记录会一起消失。 The cascade won't travel farther up the tree and delete the parent product/category table. 级联将不会沿着树走得更远,并删除父产品/类别表。

eg 例如

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

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

If you delete the 'red' category, then only the 'red' entry in the categories table dies, as well as the two entries prod/cats: 'red boots' and 'red coats'. 如果删除“红色”类别,则类别表中的“红色”条目以及两个条目prod / cats:“红色靴子”和“红色外套”都会消失。

The delete will not cascade any farther and will not take out the 'boots' and 'coats' categories. 删除操作不会进一步进行,也不会删除“靴子”和“外套”类别。

comment followup: 评论跟进:

you're still misunderstanding how cascaded deletes work. 您仍然误解了级联删除的工作方式。 They only affect the tables in which the "on delete cascade" is defined. 它们仅影响定义了“删除时级联”的表。 In this case, the cascade is set in the "categories_products" table. 在这种情况下,级联设置在“ categories_products”表中。 If you delete the 'red' category, the only records that will cascade delete in categories_products are those where category_id = red . 如果删除“红色”类别,则将在category_id = red级联删除的唯一记录是那些category_id = red It won't touch any records where 'category_id = blue', and it would not travel onwards to the "products" table, because there's no foreign key defined in that table. 它不会触及“ category_id = blue”的任何记录,也不会继续传递到“产品”表,因为该表中没有定义外键。

Here's a more concrete example: 这是一个更具体的示例:

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

Let's say you delete category #2 (blue): 假设您删除类别2(蓝色):

DELETE FROM categories WHERE (id = 2);

the DBMS will look at all the tables which have a foreign key pointing at the 'categories' table, and delete the records where the matching id is 2. Since we only defined the foreign key relationship in products_categories , you end up with this table once the delete completes: DBMS将查看所有具有指向“ categories”表的外键的表,并删除匹配ID为2的记录。由于我们仅在products_categories定义了外键关系,因此最终只能使用此表一次删除完成:

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

There's no foreign key defined in the products table, so the cascade will not work there, so you've still got boots and mittens listed. products表中没有定义外键,因此级联在那儿不起作用,因此您仍然列出了靴子和连指手套。 There's just no 'blue boots' and no 'blue mittens' anymore. 不再有“蓝色靴子”和“蓝色手套”了。

I got confused by the answer to this question, so I created a test case in MySQL, hope this helps 我对这个问题的答案感到困惑,所以我在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

I think (I'm not certain) that foreign key constraints won't do precisely what you want given your table design. 我认为(不确定)外键约束不能完全按照您的表设计来做。 Perhaps the best thing to do is to define a stored procedure that will delete a category the way you want, and then call that procedure whenever you want to delete a category. 也许最好的办法是定义一个存储过程,该存储过程将以所需的方式删除类别,然后在您要删除类别时调用该过程。

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

You also need to add the following foreign key constraints to the linking table: 您还需要将以下外键约束添加到链接表:

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

The CONSTRAINT clause can, of course, also appear in the CREATE TABLE statement. 当然,CONSTRAINT子句也可以出现在CREATE TABLE语句中。

Having created these schema objects, you can delete a category and get the behaviour you want by issuing CALL DeleteCategory(category_ID) (where category_ID is the category to be deleted), and it will behave how you want. 创建了这些架构对象之后,您可以通过发出CALL DeleteCategory(category_ID) (其中category_ID是要删除的CALL DeleteCategory(category_ID)来删除类别并获得所需的行为,并且它将按照您的意愿进行操作。 But don't issue a normal DELETE FROM query, unless you want more standard behaviour (ie delete from the linking table only, and leave the products table alone). 但是,不要发出普通的DELETE FROM查询,除非您想要更多标准行为(即,仅从链接表中删除,而保留products表)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM