2010-05-26 129 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