ข้อ จำกัด ที่สำคัญต่างประเทศ MySQL ลบน้ำตก


158

ฉันต้องการใช้กุญแจต่างประเทศเพื่อรักษาความสมบูรณ์และหลีกเลี่ยงเด็กกำพร้า (ฉันใช้ innoDB แล้ว)

ฉันจะสร้างสถานะ SQL ที่ลบบนแบบเรียงซ้อนได้อย่างไร

หากฉันลบหมวดหมู่ฉันจะแน่ใจได้อย่างไรว่าจะไม่ลบหมวดหมู่ที่เกี่ยวข้องกับหมวดหมู่อื่น

ตารางเดือย "categories_products" สร้างความสัมพันธ์แบบกลุ่มต่อกลุ่มระหว่างสองตารางอื่น ๆ

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

products
- id
- name
- price

categories_products
- categories_id
- products_id

สวัสดี - คุณอาจต้องการแก้ไขชื่อคำถามมันเป็นเรื่องเกี่ยวกับการลบน้ำตกจริง ๆ ไม่ใช่เฉพาะตารางเดือย
Paddyslacker

คำตอบ:


387

หากการเรียงซ้อนของคุณลบ nuke ผลิตภัณฑ์เพราะเป็นสมาชิกของหมวดหมู่ที่ถูกฆ่าตายแสดงว่าคุณได้ตั้งค่าคีย์ต่างประเทศของคุณไม่ถูกต้อง รับตารางตัวอย่างของคุณคุณควรมีการตั้งค่าตารางต่อไปนี้:

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;

ด้วยวิธีนี้คุณสามารถลบผลิตภัณฑ์หรือหมวดหมู่และมีเพียงระเบียนที่เกี่ยวข้องใน category_products เท่านั้นที่จะตายไปพร้อมกัน น้ำตกจะไม่เดินทางไกลขึ้นไปบนต้นไม้และลบตารางผลิตภัณฑ์ / หมวดหมู่หลัก

เช่น

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

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

หากคุณลบหมวดหมู่ 'สีแดง' จะมีเพียงรายการ 'สีแดง' ในตารางหมวดหมู่เท่านั้นที่จะตายรวมถึงผลิตภัณฑ์สองรายการที่แยง / แมว: 'รองเท้าบูทสีแดง' และ 'เสื้อโค้ทสีแดง'

การลบจะไม่ทำให้เกิดความเสียหายใด ๆ และจะไม่นำหมวดหมู่ 'รองเท้า' และ 'เสื้อโค้ท' ออก

การติดตามความคิดเห็น:

คุณยังคงเข้าใจผิดว่าการลบแบบเรียงซ้อนทำงานอย่างไร จะมีผลกับตารางที่กำหนด "on delete cascade" เท่านั้น ในกรณีนี้การเรียงซ้อนจะถูกตั้งค่าในตาราง "categories_products" ถ้าคุณลบหมวดหมู่ 'สีแดง' ระเบียนเดียวที่น้ำตกจะลบใน 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 จะดูตารางทั้งหมดที่มีคีย์ต่างประเทศที่ชี้ไปที่ตาราง 'หมวดหมู่' และลบระเบียนที่ตรงกับ id เป็น 2 เนื่องจากเรากำหนดความสัมพันธ์ของคีย์ต่างประเทศproducts_categoriesเท่านั้นคุณจะสิ้นสุดด้วยตารางนี้เมื่อ ลบเสร็จสิ้น:

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

ไม่มีรหัสต่างประเทศที่กำหนดไว้ในproductsตารางดังนั้นน้ำตกจะไม่ทำงานที่นั่นดังนั้นคุณยังมีรายการรองเท้าและถุงมือ ไม่มี 'รองเท้าบูทสีน้ำเงิน' และ 'ถุงมือสีฟ้า' อีกต่อไป


ฉันคิดว่าฉันเขียนคำถามผิดวิธี หากฉันลบหมวดหมู่ฉันจะแน่ใจได้อย่างไรว่าจะไม่ลบหมวดหมู่ที่เกี่ยวข้องกับหมวดหมู่อื่น
Cudos

36
นี่เป็นคำตอบที่ยอดเยี่ยมมากมีมุมมองสูงมากและมีภาพประกอบที่เยี่ยมยอด ขอบคุณที่สละเวลาเขียนมันออกมาทั้งหมด
กอตต์

2
เมื่อสร้างตารางคุณต้องระบุ InnoDB หรือเอ็นจิน MySQL ตัวอื่นที่สามารถCASCADEใช้งานได้ ไม่เช่นนั้นค่าเริ่มต้นของ MySQL MyISAM จะถูกนำมาใช้และ MyISAM ไม่สนับสนุนCASCADEการดำเนินการ หากต้องการทำสิ่งนี้เพียงเพิ่มENGINE InnoDBก่อนหน้า;นี้
แพทริค

11

ฉันสับสนกับคำตอบของคำถามนี้ดังนั้นฉันจึงสร้างกรณีทดสอบใน 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

8

ฉันคิดว่า (ฉันไม่แน่ใจ) ว่าข้อ จำกัด ของคีย์ต่างประเทศจะไม่ทำสิ่งที่คุณต้องการอย่างถูกต้องเนื่องจากการออกแบบตารางของคุณ บางทีสิ่งที่ดีที่สุดที่ต้องทำคือการกำหนดโพรซีเดอร์ที่เก็บไว้ซึ่งจะลบหมวดหมู่ในแบบที่คุณต้องการแล้วเรียกขั้นตอนนั้นเมื่อใดก็ตามที่คุณต้องการลบหมวดหมู่

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

คุณต้องเพิ่มข้อ จำกัด foreign key ต่อไปนี้ในตารางการเชื่อมโยง:

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

แน่นอนว่าคำสั่ง CONSTRAINT สามารถปรากฏในคำสั่ง CREATE TABLE ได้เช่นกัน

เมื่อสร้างวัตถุสคีมาเหล่านี้แล้วคุณสามารถลบหมวดหมู่และรับพฤติกรรมที่คุณต้องการโดยออกCALL DeleteCategory(category_ID)(โดยที่ category_ID คือหมวดหมู่ที่จะลบ) และจะทำงานตามที่คุณต้องการ แต่ไม่ต้องออกDELETE FROMแบบสอบถามปกติยกเว้นว่าคุณต้องการพฤติกรรมมาตรฐานมากขึ้น (เช่นลบออกจากตารางเชื่อมโยงเท่านั้นและปล่อยให้productsตารางอยู่คนเดียว)


ฉันคิดว่าฉันเขียนคำถามผิดวิธี หากฉันลบหมวดหมู่ฉันจะแน่ใจได้อย่างไรว่าจะไม่ลบหมวดหมู่ที่เกี่ยวข้องกับหมวดหมู่อื่น
Cudos

ตกลงในกรณีนี้ฉันคิดว่าคำตอบของ Marc B ทำในสิ่งที่คุณต้องการ
Hammerite

สวัสดี @Hammerite คุณช่วยบอกหน่อยได้ไหมว่าอะไรคือความหมายของKEY pkey (product_id),คำที่สามCREATE TABLEในคำตอบที่ยอมรับ?
Siraj Alam
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.