MySQL ràng buộc khóa ngoại, xóa tầng


158

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

Làm cách nào để tạo một thống kê SQL XÓA trên CASCADE?

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

Bảng xoay vòng "chuyên mục" sản phẩm "tạo ra 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

Xin chào - bạn có thể muốn sửa đổi tiêu đề câu hỏi, đó là về việc xóa tầng thực sự, chứ không phải các bảng xoay vòng cụ thể.
Paddyslacker

Câu trả lời:


386

Nếu xếp tầng của bạn xóa nuke một sản phẩm vì đó 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 mình không đúng cách. Đưa ra các bảng ví dụ của bạn, bạn sẽ 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 danh mục và chỉ các bản ghi được liên kết trong chuyên mục_products sẽ chết cùng. Dòng thác sẽ không đi xa hơn trên cây và xóa bảng danh mục / sản phẩm 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 danh mục 'đỏ', thì chỉ có mục 'đỏ' trong bảng danh mục bị chết, cũng như hai mục prod / cat: 'ủng đỏ' và 'áo khoác đỏ'.

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

theo dõi bình luận:

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

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

Giả sử bạn xóa danh mục số 2 (màu xanh):

DELETE FROM categories WHERE (id = 2);

DBMS sẽ xem xét tất cả các bảng có khóa ngoại trỏ vào bảng 'danh mục' và xóa các bản ghi trong đó id khớ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 một lần xóa hoàn thành:

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

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


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

36
Đây là một câu trả lời thực sự tuyệt vời, rất khó hiểu và minh họa tuyệt vời. Cảm ơn đã dành thời gian để viết tất cả ra.
scottb

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

11

Tôi đã bối rối trước 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

8

Tôi nghĩ (tôi không chắc chắn) rằng các 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 đưa ra cho thiết kế bảng của bạn. Có lẽ điều tốt nhất để làm là xác định một thủ tục được lưu trữ 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 thêm các ràng buộc khóa 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

Tất nhiên, mệnh đề CONSTRAINT cũng có thể xuất hiện trong câu lệnh CREATE TABLE.

Khi đã tạo các đối tượng lược đồ này, bạn có thể xóa một danh mục và có được hành vi bạn muốn bằng cách ban hành CALL DeleteCategory(category_ID)(trong đó category_ID là danh mục cần xóa) và nó sẽ hành xử theo cách bạn muốn. Nhưng đừng đưa ra một DELETE FROMtruy vấn bình thường , trừ khi bạn muốn có hành vi chuẩn hơn (nghĩa là chỉ xóa khỏi bảng liên kết và để lại productsbảng một mình).


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

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

Xin chào @ Hammerite, bạn có thể vui lòng cho tôi biết ý nghĩa của truy vấn KEY pkey (product_id),thứ ba CREATE TABLEtrong câu trả lời được chấp nhận là gì không?
Siraj Alam
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.