2010-05-26 57 views
124

Tôi muốn sử dụng các khóa ngoại để giữ tính toàn vẹn và tránh các trẻ mồ côi (tôi đã sử dụng innoDB).Ràng buộc khóa ngoài của MySQL, xóa tầng

Làm cách nào để tạo một chỉ số SQL DELETE ON CASCADE?

Nếu tôi xóa danh mục thì làm cách nào để đảm bảo rằng nó sẽ không xóa các sản phẩm cũng có liên quan đến các danh mục khác.

Bảng tổng hợp "categories_products" tạo mối quan hệ nhiều-nhiều giữa hai bảng khác.

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

products 
- id 
- name 
- price 

categories_products 
- categories_id 
- products_id 
+0

Hi - bạn có thể muốn thay đổi tiêu đề câu hỏi, đó là khoảng cascade xóa thực sự, chứ không phải cụ thể bảng tổng hợp. – Paddyslacker

Trả lời

333

Nếu tầng của bạn xóa nuke một sản phẩm vì nó là thành viên của danh mục bị giết, thì bạn đã thiết lập khóa ngoại của bạn không đúng cách. Với bảng ví dụ của bạn, bạn nên có các thiết lập bảng sau:

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; 

Bằng cách này, bạn có thể xóa một sản phẩm hoặc một loại, và chỉ có các hồ sơ liên quan trong categories_products sẽ chết cùng. Các thác sẽ không đi xa hơn cây và xóa bảng sản phẩm/danh mục gốc.

ví dụ:

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

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

Nếu bạn xóa các loại 'đỏ', sau đó chỉ 'đỏ' entry trong bảng categories chết, cũng như hai mục sản/mèo: 'khởi động đỏ' và 'áo đỏ'.

Việc xóa sẽ không xếp hàng xa hơn và sẽ không loại bỏ danh mục 'khởi động' và 'áo khoác'.

nhận xét tiếp theo:

bạn vẫn hiểu lầm về cách xóa tầng hoạt động. Chúng chỉ ảnh hưởng đến các bảng trong đó "thác trên xóa" được xác định. Trong trường hợp này, tầng được đặt trong bảng "categories_products". Nếu bạn xóa danh mục 'màu đỏ', các bản ghi duy nhất sẽ xếp xóa trong danh mục_products là những mục có số category_id = red. Nó sẽ không chạm vào bất kỳ bản ghi nào có 'category_id = blue' và nó sẽ không chuyển sang bảng "sản phẩm", bởi vì không có khóa ngoại được xác định trong bảng đó.

Dưới đây là một ví dụ cụ thể hơn:

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

Hãy nói rằng bạn xóa danh mụC# 2 (màu xanh):

DELETE FROM categories WHERE (id = 2); 

DBMS sẽ xem xét tất cả các bảng trong đó có một trỏ chính nước ngoài tại bảng 'danh mục' và xóa các bản ghi trong đó id phù hợp là 2. Vì chúng tôi chỉ xác định mối quan hệ khóa ngoài trong products_categories, bạn kết thúc với bảng này khi xóa hoàn tất:

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

Không có khóa ngoại được xác định trong bảng products, do đó, thác sẽ không hoạt động ở đó, vì vậy bạn vẫn có khởi động và găng được liệt kê. Không chỉ có 'đôi bốt xanh' và không còn 'găng tay xanh' nữa.

+0

Tôi nghĩ rằng tôi đã viết câu hỏi của tôi một cách sai lầm. Nếu tôi xóa một danh mục thì làm cách nào để đảm bảo rằng nó sẽ không xóa các sản phẩm cũng có liên quan đến các danh mục khác. – Cudos

+25

Đây là câu trả lời tuyệt vời, rất dễ thấy và được minh họa tuyệt vời. Cảm ơn bạn đã dành thời gian viết tất cả. – scottb

+1

Khi tạo các bảng, bạn cần phải chỉ định InnoDB hoặc một công cụ MySQL khác có khả năng hoạt động 'CASCADE'. Nếu không, mặc định MySQL, MyISAM, sẽ được sử dụng và MyISAM không hỗ trợ các hoạt động 'CASCADE'. Để thực hiện điều này, chỉ cần thêm 'ENGINE InnoDB' vào trước'; 'cuối cùng. – Patrick

7

Tôi nghĩ rằng (tôi không chắc chắn) ràng buộc khóa ngoại sẽ không thực hiện chính xác những gì bạn muốn cho thiết kế bảng của mình. Có lẽ điều tốt nhất cần làm là xác định một thủ tục lưu sẵn sẽ xóa một danh mục theo cách bạn muốn, và sau đó gọi thủ tục đó bất cứ khi nào bạn muốn xóa một danh mục.

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 

Bạn cũng cần phải thêm ràng buộc khoá ngoại sau vào bảng liên kết:

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 

Mệnh đề CONSTRAINT có thể, tất nhiên, cũng xuất hiện trong câu lệnh CREATE TABLE. Sau khi đã tạo các đối tượng lược đồ này, bạn có thể xóa một danh mục và nhận hành vi bạn muốn bằng cách phát hành CALL DeleteCategory(category_ID) (trong đó category_ID là danh mục sẽ bị xóa) và nó sẽ hoạt động theo cách bạn muốn. Nhưng đừng đưa ra truy vấn thông thường DELETE FROM, trừ khi bạn muốn có hành vi tiêu chuẩn hơn (nghĩa là chỉ xóa bảng kết nối và để riêng bảng products).

+0

Tôi nghĩ tôi đã viết câu hỏi sai. Nếu tôi xóa một danh mục thì làm cách nào để đảm bảo rằng nó sẽ không xóa các sản phẩm cũng có liên quan đến các danh mục khác. – Cudos

+0

ok tốt trong trường hợp đó tôi nghĩ rằng câu trả lời của Marc B làm những gì bạn muốn. – Hammerite

9

tôi đã nhầm lẫn bởi câu trả lời cho câu hỏi này, vì vậy tôi đã tạo ra một trường hợp thử nghiệm trong MySQL, hy vọng điều này sẽ giúp

-- 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 
Các vấn đề liên quan