Vincoli di chiave esterna MySQL, eliminazione in cascata


158

Voglio usare le chiavi esterne per mantenere l'integrità ed evitare gli orfani (uso già innoDB).

Come faccio a creare una dichiarazione SQL che ELIMINA SU CASCADE?

Se elimino una categoria, come posso assicurarmi che non elimini i prodotti correlati anche ad altre categorie.

La tabella pivot "categorie_prodotti" crea una relazione molti-a-molti tra le altre due tabelle.

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

products
- id
- name
- price

categories_products
- categories_id
- products_id

Ciao, potresti voler modificare il titolo della domanda, si tratta davvero di eliminazioni a cascata, non specificamente di tabelle pivot.
Paddyslacker

Risposte:


387

Se il tuo cascading elimina il nuke di un prodotto perché apparteneva a una categoria che è stata uccisa, allora hai impostato le tue chiavi esterne in modo errato. Date le vostre tabelle di esempio, dovreste avere la seguente configurazione della tabella:

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;

In questo modo, puoi eliminare un prodotto O una categoria e solo i record associati in categorie_prodotti moriranno insieme. La cascata non si sposterà più in alto sull'albero ed eliminerà la tabella del prodotto / categoria principale.

per esempio

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

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

Se elimini la categoria 'rosso', allora muore solo la voce 'rossa' nella tabella delle categorie, così come le due voci prod / cats: 'stivali rossi' e 'cappotti rossi'.

L'eliminazione non scenderà più a cascata e non eliminerà le categorie "stivali" e "cappotti".

follow-up commento:

stai ancora fraintendendo come funzionano le eliminazioni in cascata. Hanno effetto solo sulle tabelle in cui è definito "on delete cascade". In questo caso, la cascata è impostata nella tabella "categorie_prodotti". Se si elimina la categoria "rossa", gli unici record che verranno eliminati in cascata in categorie_prodotti sono quelli in cui category_id = red. Non toccherà alcun record in cui 'category_id = blue', e non si sposterà in avanti alla tabella "prodotti", perché non esiste una chiave esterna definita in quella tabella.

Ecco un esempio più concreto:

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

Supponiamo che tu elimini la categoria # 2 (blu):

DELETE FROM categories WHERE (id = 2);

il DBMS esaminerà tutte le tabelle che hanno una chiave esterna che punta alla tabella 'categorie' ed eliminerà i record in cui l'id corrispondente è 2. Poiché abbiamo definito la relazione di chiave esterna solo products_categories, si finisce con questa tabella una volta che l'eliminazione completa:

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

Non c'è una chiave esterna definita nella productstabella, quindi la cascata non funzionerà lì, quindi hai ancora stivali e guanti elencati. Non ci sono più "stivali blu" e "guanti blu".


Penso di aver scritto la mia domanda nel modo sbagliato. Se elimino una categoria, come posso assicurarmi che non elimini i prodotti correlati anche ad altre categorie.
Cudos,

36
Questa è una risposta davvero grandiosa, molto appariscente e meravigliosamente illustrata. Grazie per aver dedicato del tempo a scrivere tutto.
Scott

2
Quando si creano le tabelle, è necessario specificare InnoDB o un altro motore MySQL in grado di CASCADEfunzionare. Altrimenti verrà utilizzato il valore predefinito di MySQL, MyISAM, e MyISAM non supporta le CASCADEoperazioni. Per fare ciò, basta aggiungere ENGINE InnoDBprima dell'ultimo ;.
Patrick,

11

Sono stato confuso dalla risposta a questa domanda, quindi ho creato un caso di prova in MySQL, spero che questo aiuti

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

Penso (non sono sicuro) che i vincoli di chiave esterna non faranno esattamente quello che vuoi dato il design del tuo tavolo. Forse la cosa migliore da fare è definire una procedura memorizzata che eliminerà una categoria nel modo desiderato, quindi chiamare quella procedura ogni volta che si desidera eliminare una categoria.

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

È inoltre necessario aggiungere i seguenti vincoli di chiave esterna alla tabella di collegamento:

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

La clausola CONSTRAINT può, ovviamente, apparire anche nell'istruzione CREATE TABLE.

Dopo aver creato questi oggetti dello schema, è possibile eliminare una categoria e ottenere il comportamento desiderato emettendo CALL DeleteCategory(category_ID)(dove ID_categoria è la categoria da eliminare) e si comporterà come desiderato. Non emettere una DELETE FROMquery normale , a meno che non si desideri un comportamento più standard (ovvero eliminare solo dalla tabella di collegamento e lasciare la productstabella da sola).


Penso di aver scritto la mia domanda nel modo sbagliato. Se elimino una categoria, come posso assicurarmi che non elimini i prodotti correlati anche ad altre categorie.
Cudos,

ok bene in quel caso penso che la risposta di Marc B faccia quello che vuoi.
Hammerite,

Ciao @Hammerite, puoi dirmi qual è il significato della KEY pkey (product_id),terza CREATE TABLEquery nella risposta accettata?
Siraj Alam,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.