Kendala kunci asing MySQL, penghapusan kaskade

158

Saya ingin menggunakan kunci asing untuk menjaga integritas dan menghindari anak yatim (saya sudah menggunakan innoDB).

Bagaimana cara membuat statemen SQL yang HAPUS DI CASCADE?

Jika saya menghapus kategori maka bagaimana saya memastikan bahwa itu tidak akan menghapus produk yang juga terkait dengan kategori lain.

Tabel pivot "categories_products" menciptakan hubungan banyak-ke-banyak antara dua tabel lainnya.

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

products
- id
- name
- price

categories_products
- categories_id
- products_id
Cudos
sumber
Hai - Anda mungkin ingin mengubah judul pertanyaan, ini tentang penghapusan kaskade benar-benar, tidak secara khusus tabel pivot.
Paddyslacker

Jawaban:

387

Jika cascading Anda menghapus nuke produk karena itu adalah anggota dari kategori yang terbunuh, maka Anda telah mengatur kunci asing Anda dengan tidak tepat. Diberikan tabel contoh Anda, Anda harus memiliki pengaturan tabel berikut:

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;

Dengan cara ini, Anda dapat menghapus suatu produk ATAU suatu kategori, dan hanya catatan yang terkait dalam kategori_produk yang akan mati bersama. Kaskade tidak akan berjalan lebih jauh ke pohon dan menghapus tabel kategori produk / induk.

misalnya

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

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

Jika Anda menghapus kategori 'merah', maka hanya entri 'merah' di tabel kategori yang mati, serta dua entri prod / kucing: 'sepatu merah' dan 'mantel merah'.

Penghapusan tidak akan menyebabkan penurunan lebih jauh dan tidak akan menghapus kategori 'sepatu boot' dan 'mantel'.

tindak lanjut komentar:

Anda masih salah paham bagaimana cascade delases bekerja. Mereka hanya mempengaruhi tabel di mana "pada penghapusan kaskade" didefinisikan. Dalam kasus ini, kaskade diatur dalam tabel "categories_products". Jika Anda menghapus kategori 'merah', satu-satunya catatan yang akan mengalir menghapus dalam kategori_produk adalah yang ada di mana category_id = red. Itu tidak akan menyentuh catatan mana pun di mana 'category_id = biru', dan itu tidak akan melanjutkan perjalanan ke tabel "produk", karena tidak ada kunci asing yang ditentukan dalam tabel itu.

Inilah contoh yang lebih konkret:

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

Katakanlah Anda menghapus kategori # 2 (biru):

DELETE FROM categories WHERE (id = 2);

DBMS akan melihat semua tabel yang memiliki kunci asing menunjuk pada tabel 'kategori', dan menghapus catatan di mana id yang cocok adalah 2. Karena kami hanya mendefinisikan hubungan kunci asing di products_categories, Anda berakhir dengan tabel ini setelah hapus selesai:

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

Tidak ada kunci asing yang ditentukan dalam productstabel, sehingga kaskade tidak akan berfungsi di sana, jadi Anda masih memiliki bot dan sarung tangan terdaftar. Tidak ada lagi 'sepatu bot biru' dan tidak ada 'sarung tangan biru' lagi.

Marc B
sumber
Saya pikir saya salah menuliskan pertanyaan saya. Jika saya menghapus kategori maka bagaimana saya memastikan bahwa itu tidak akan menghapus produk yang juga terkait dengan kategori lain.
Cudos
36
Ini adalah jawaban yang benar-benar hebat, sangat tajam, dan sangat bagus. Terima kasih telah meluangkan waktu untuk menulis semuanya.
scottb
2
Saat membuat tabel, Anda perlu menentukan InnoDB atau mesin MySQL lain yang mampu CASCADEberoperasi. Kalau tidak, standar MySQL, MyISAM, akan digunakan dan MyISAM tidak mendukung CASCADEoperasi. Untuk melakukan ini, tambahkan saja ENGINE InnoDBsebelum yang terakhir ;.
Patrick
11

Saya bingung dengan jawaban untuk pertanyaan ini, jadi saya membuat test case di MySQL, semoga ini membantu

-- 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
Abderrahim
sumber
8

Saya pikir (saya tidak yakin) bahwa batasan kunci asing tidak akan melakukan apa yang Anda inginkan dengan desain meja Anda. Mungkin hal terbaik untuk dilakukan adalah mendefinisikan prosedur tersimpan yang akan menghapus kategori seperti yang Anda inginkan, dan kemudian memanggil prosedur itu setiap kali Anda ingin menghapus kategori.

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

Anda juga perlu menambahkan batasan kunci asing berikut ke tabel tautan:

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

Klausa CONSTRAINT dapat, tentu saja, juga muncul dalam pernyataan CREATE TABLE.

Setelah membuat objek skema ini, Anda dapat menghapus kategori dan mendapatkan perilaku yang Anda inginkan dengan mengeluarkan CALL DeleteCategory(category_ID)(di mana category_ID adalah kategori yang akan dihapus), dan itu akan berperilaku seperti yang Anda inginkan. Tetapi jangan mengeluarkan DELETE FROMkueri normal , kecuali jika Anda menginginkan lebih banyak perilaku standar (yaitu, hapus hanya dari tabel yang menautkan, dan biarkan productstabel itu sendiri).

Hammerite
sumber
Saya pikir saya salah menuliskan pertanyaan saya. Jika saya menghapus kategori maka bagaimana saya memastikan bahwa itu tidak akan menghapus produk yang juga terkait dengan kategori lain.
Cudos
ok baik dalam hal itu saya pikir jawaban Marc B melakukan apa yang Anda inginkan.
Hammerite
Halo @Hammerite, bisakah Anda memberi tahu saya apa arti dari pertanyaan KEY pkey (product_id),ketiga CREATE TABLEdalam jawaban yang diterima?
Siraj Alam