Ograniczenia klucza obcego MySQL, usuwanie kaskadowe

158

Chcę używać kluczy obcych, aby zachować integralność i uniknąć sierot (używam już innoDB).

Jak zrobić instrukcję SQL, która USUWA W KASKADIE?

Jeśli usunę kategorię, w jaki sposób mogę się upewnić, że nie usunie ona produktów, które są również powiązane z innymi kategoriami.

Tabela przestawna „category_products” tworzy relację wiele-do-wielu między dwiema pozostałymi tabelami.

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

products
- id
- name
- price

categories_products
- categories_id
- products_id
Cudos
źródło
Cześć - możesz chcieć zmodyfikować tytuł pytania, tak naprawdę chodzi o kaskadowe usuwanie, a nie konkretnie tabele przestawne.
Paddyslacker

Odpowiedzi:

387

Jeśli kaskadowanie usuwa produkt nuklearny, ponieważ należał on do kategorii, która została zabita, oznacza to, że nieprawidłowo skonfigurowałeś klucze obce. Biorąc pod uwagę przykładowe tabele, powinieneś mieć następującą konfigurację tabeli:

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;

W ten sposób możesz usunąć produkt LUB kategorię, a tylko powiązane rekordy w category_products umrą obok. Kaskada nie przesunie się dalej w górę drzewa i nie usunie nadrzędnej tabeli produktów / kategorii.

na przykład

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

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

Jeśli usuniesz kategorię „czerwone”, umiera tylko wpis „czerwony” w tabeli kategorii, a także dwa wpisy prod / cats: „czerwone buty” i „czerwone płaszcze”.

Usunięcie nie nastąpi dalej i nie spowoduje usunięcia kategorii „buty” i „płaszcze”.

uzupełnienie komentarza:

nadal nie rozumiesz, jak działa usuwanie kaskadowe. Mają one wpływ tylko na tabele, w których zdefiniowano „kaskadę usuwania”. W tym przypadku kaskada jest ustawiana w tabeli „Categories_products”. Jeśli usuniesz kategorię „czerwona”, jedynymi rekordami, które będą usuwane kaskadowo w kategoriach category_products, są te, w których category_id = red. Nie dotknie żadnych rekordów, w których „category_id = blue” i nie przejdzie dalej do tabeli „products”, ponieważ nie ma zdefiniowanego klucza obcego w tej tabeli.

Oto bardziej konkretny przykład:

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

Załóżmy, że usuwasz kategorię nr 2 (niebieski):

DELETE FROM categories WHERE (id = 2);

DBMS przejrzy wszystkie tabele, które mają klucz obcy wskazujący na tabelę „kategorie” i usunie rekordy, w których pasujący identyfikator wynosi 2. Ponieważ zdefiniowaliśmy tylko relację klucza obcego w programie products_categories, otrzymujesz tę tabelę, gdy usuwanie kończy się:

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

W productstabeli nie zdefiniowano żadnego klucza obcego , więc kaskada nie będzie tam działać, więc nadal masz wymienione buty i rękawice. Po prostu nie ma już „niebieskich butów” i „niebieskich rękawiczek”.

Marc B.
źródło
Myślę, że źle napisałem pytanie. Jeśli usunę kategorię, w jaki sposób mogę się upewnić, że nie usunie ona produktów, które są również powiązane z innymi kategoriami.
Cudos
36
To naprawdę świetna, bardzo zrozumiała i cudownie zilustrowana odpowiedź. Dzięki za poświęcenie czasu na napisanie tego wszystkiego.
scottb
2
Podczas tworzenia tabel musisz określić InnoDB lub inny silnik MySQL, który może CASCADEdziałać. W przeciwnym razie zostanie użyty domyślny MySQL, MyISAM, a MyISAM nie obsługuje CASCADEoperacji. Aby to zrobić, po prostu dodaj ENGINE InnoDBprzed ostatnim ;.
Patrick
11

Zmyliła mnie odpowiedź na to pytanie, więc stworzyłem przypadek testowy w MySQL, mam nadzieję, że to pomoże

-- 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
źródło
8

Myślę (nie jestem pewien), że ograniczenia klucza obcego nie będą działać dokładnie tak, jak chcesz, biorąc pod uwagę projekt tabeli. Być może najlepszą rzeczą do zrobienia jest zdefiniowanie procedury składowanej, która usunie kategorię tak, jak chcesz, a następnie wywołanie tej procedury za każdym razem, gdy chcesz usunąć 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

Musisz również dodać następujące ograniczenia klucza obcego do tabeli łączącej:

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

Klauzula CONSTRAINT może oczywiście pojawić się również w instrukcji CREATE TABLE.

Po utworzeniu tych obiektów schematu możesz usunąć kategorię i uzyskać żądane zachowanie, wydając CALL DeleteCategory(category_ID)(gdzie category_ID jest kategorią do usunięcia), a ona będzie zachowywać się tak, jak chcesz. Ale nie wysyłaj normalnego DELETE FROMzapytania, chyba że chcesz bardziej standardowego zachowania (tj. Usuń tylko z tabeli łączącej i pozostaw productstabelę w spokoju).

Hammerite
źródło
Myślę, że źle napisałem pytanie. Jeśli usunę kategorię, w jaki sposób mogę się upewnić, że nie usunie ona produktów, które są również powiązane z innymi kategoriami.
Cudos
ok, w takim razie myślę, że odpowiedź Marca B spełnia Twoje oczekiwania.
Hammerite
Witaj @Hammerite, czy możesz mi powiedzieć, jakie jest znaczenie KEY pkey (product_id),w trzecim CREATE TABLEzapytaniu w zaakceptowanej odpowiedzi?
Siraj Alam