MySQL-Fremdschlüsseleinschränkungen, Kaskadenlöschung

158

Ich möchte Fremdschlüssel verwenden, um die Integrität zu erhalten und Waisen zu vermeiden (ich verwende bereits innoDB).

Wie erstelle ich eine SQL-Anweisung, die auf CASCADE LÖSCHT?

Wenn ich eine Kategorie lösche, wie stelle ich dann sicher, dass keine Produkte gelöscht werden, die auch mit anderen Kategorien verknüpft sind.

Die Pivot-Tabelle "category_products" erstellt eine Viele-zu-Viele-Beziehung zwischen den beiden anderen Tabellen.

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

products
- id
- name
- price

categories_products
- categories_id
- products_id
Cudos
quelle
Hallo - vielleicht möchten Sie den Fragentitel ändern, es geht wirklich um Kaskadenlöschungen, nicht speziell um Pivot-Tabellen.
Paddyslacker

Antworten:

385

Wenn Ihre Kaskadierung ein Produkt löscht, weil es Mitglied einer Kategorie war, die getötet wurde, haben Sie Ihre Fremdschlüssel nicht ordnungsgemäß eingerichtet. In Anbetracht Ihrer Beispieltabellen sollten Sie die folgende Tabelle einrichten:

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;

Auf diese Weise können Sie ein Produkt ODER eine Kategorie löschen, und nur die zugehörigen Datensätze in category_products sterben daneben. Die Kaskade bewegt sich nicht weiter nach oben und löscht die übergeordnete Produkt- / Kategorietabelle.

z.B

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

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

Wenn Sie die Kategorie 'rot' löschen, stirbt nur der Eintrag 'rot' in der Kategorietabelle sowie die beiden Einträge prod / cat: 'rote Stiefel' und 'rote Mäntel'.

Das Löschen wird nicht weiter kaskadiert und die Kategorien "Stiefel" und "Mäntel" werden nicht entfernt.

Kommentar Follow-up:

Sie verstehen immer noch falsch, wie kaskadierte Löschvorgänge funktionieren. Sie wirken sich nur auf die Tabellen aus, in denen die "On Delete Cascade" definiert ist. In diesem Fall wird die Kaskade in der Tabelle "category_products" festgelegt. Wenn Sie die Kategorie 'rot' löschen, sind die einzigen Datensätze, die in Kategorien_Produkten kaskadiert werden, diejenigen, bei denen category_id = red. Es werden keine Datensätze berührt, bei denen 'category_id = blue' ist, und es wird nicht zur Tabelle "products" weitergeleitet, da in dieser Tabelle kein Fremdschlüssel definiert ist.

Hier ist ein konkreteres Beispiel:

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

Angenommen, Sie löschen Kategorie 2 (blau):

DELETE FROM categories WHERE (id = 2);

Das DBMS überprüft alle Tabellen, deren Fremdschlüssel auf die Tabelle 'Kategorien' verweist, und löscht die Datensätze, in denen die übereinstimmende ID 2 lautet. Da wir nur die Fremdschlüsselbeziehung in definiert haben products_categories, erhalten Sie diese Tabelle einmal am Löschen abgeschlossen:

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

In der productsTabelle ist kein Fremdschlüssel definiert , sodass die Kaskade dort nicht funktioniert. Sie haben also immer noch Stiefel und Fäustlinge aufgelistet. Es gibt nur noch keine "blauen Stiefel" und keine "blauen Fäustlinge" mehr.

Marc B.
quelle
Ich glaube, ich habe meine Frage falsch geschrieben. Wenn ich eine Kategorie lösche, wie stelle ich dann sicher, dass keine Produkte gelöscht werden, die sich auch auf andere Kategorien beziehen.
Cudos
36
Dies ist eine wirklich großartige, sehr übersichtliche und wunderbar illustrierte Antwort. Vielen Dank, dass Sie sich die Zeit genommen haben, alles aufzuschreiben.
Scottb
2
Beim Erstellen der Tabellen müssen Sie InnoDB oder eine andere MySQL-Engine angeben, die CASCADEbetriebsfähig ist. Andernfalls wird der MySQL-Standard MyISAM verwendet und MyISAM unterstützt keine CASCADEVorgänge. Fügen Sie dazu einfach ENGINE InnoDBvor dem letzten hinzu ;.
Patrick
11

Die Antwort auf diese Frage hat mich verwirrt und ich habe einen Testfall in MySQL erstellt. Ich hoffe, das hilft

-- 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
quelle
8

Ich denke (ich bin nicht sicher), dass Fremdschlüsseleinschränkungen angesichts Ihres Tabellendesigns nicht genau das tun, was Sie wollen. Am besten definieren Sie eine gespeicherte Prozedur, mit der eine Kategorie nach Ihren Wünschen gelöscht wird, und rufen diese Prozedur dann auf, wenn Sie eine Kategorie löschen möchten.

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

Sie müssen der Verknüpfungstabelle auch die folgenden Fremdschlüsseleinschränkungen hinzufügen:

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

Die CONSTRAINT-Klausel kann natürlich auch in der Anweisung CREATE TABLE enthalten sein.

Nachdem Sie diese Schemaobjekte erstellt haben, können Sie eine Kategorie löschen und das gewünschte Verhalten erhalten, indem Sie Folgendes ausgeben CALL DeleteCategory(category_ID)(wobei category_ID die zu löschende Kategorie ist). Das Verhalten verhält sich dann wie gewünscht. Geben Sie jedoch keine normale DELETE FROMAbfrage aus, es sei denn, Sie möchten mehr Standardverhalten (dh nur aus der Verknüpfungstabelle löschen und die productsTabelle in Ruhe lassen).

Hammerit
quelle
Ich glaube, ich habe meine Frage falsch geschrieben. Wenn ich eine Kategorie lösche, wie stelle ich dann sicher, dass keine Produkte gelöscht werden, die sich auch auf andere Kategorien beziehen.
Cudos
ok gut in diesem Fall denke ich, dass Marc B's Antwort macht, was Sie wollen.
Hammerite
Hallo @Hammerite, kannst du mir bitte sagen, was KEY pkey (product_id),die dritte CREATE TABLEAbfrage in der akzeptierten Antwort bedeutet?
Siraj Alam