2010-05-26 19 views
124

Desidero utilizzare chiavi esterne per mantenere l'integrità ed evitare orfani (utilizzo già innoDB).Vincoli chiave esterna MySQL, eliminazione cascata

Come si crea una statistica SQL che ELIMINA SU CASCADE?

Se elimino una categoria, come faccio ad accertarmi che non elimini i prodotti che sono anche correlati ad altre categorie.

La tabella pivot "categories_products" 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 
+0

Hi - si potrebbe desiderare di modificare il titolo della domanda, si tratta di cascata elimina in realtà, non in particolare tabelle pivot. – Paddyslacker

risposta

333

Se il collegamento a cascata elimina un prodotto a causa di un membro di una categoria che è stato ucciso, le chiavi esterne sono state configurate in modo errato. Dato il tuo esempio tabelle, si dovrebbe avere la seguente impostazione 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, è possibile eliminare un prodotto o di una categoria, e solo i record associati a categories_products moriranno insieme. La cascata non si sposta più in alto sull'albero ed elimina la tabella prodotto/categoria padre.

ad es.

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

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

Se si elimina la categoria 'rosso', poi solo la voce 'rosso' nella tabella categorie muore, così come le due voci prod/gatti: 'stivali rossi' e 'giubbe rosse'.

L'eliminazione non si sovrapporrà ulteriormente e non eliminerà le categorie "stivali" e "cappotti".

commento followup:

stai ancora equivoco come cascata elimina il lavoro. Hanno effetto solo sulle tabelle in cui è definito "on delete cascade". In questo caso, la cascata viene impostata nella tabella "categories_products". Se elimini la categoria "rossa", gli unici record che verranno eliminati a cascata in categories_products sono quelli in cui category_id = red. Non toccherà alcun record in cui 'category_id = blue', e non viaggerà verso la tabella "prodotti", perché non ci sono chiavi esterne definite 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 
+------------+-------------+ 

Diciamo che si elimina categoria # 2 (blu):

DELETE FROM categories WHERE (id = 2); 

il DBMS esaminerà tutti i tavoli che hanno una punta chiave esterna a la tabella "categorie" ed elimina i record in cui l'ID corrispondente è 2. Poiché abbiamo definito la relazione di chiave esterna solo in products_categories, si finisce con questa tabella una volta completata l'eliminazione:

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

Non c'è chiave esterna definita nella tabella products, quindi la cascata non funziona lì, così hai ancora stivali e guanti elencati. Non ci sono più "stivali blu" e "muffole blu".

+0

Penso di aver scritto la mia domanda nel modo sbagliato. Se elimino una categoria, come faccio a verificare che non elimini i prodotti che sono correlati ad altre categorie. – Cudos

+25

Questa è una risposta davvero fantastica, molto perspicua e meravigliosamente illustrata. Grazie per aver dedicato del tempo a scrivere tutto. – scottb

+1

Quando si creano le tabelle, è necessario specificare InnoDB o un altro motore MySQL in grado di eseguire operazioni 'CASCADE'. In caso contrario verrà utilizzato il valore predefinito MySQL, MyISAM, e MyISAM non supporta le operazioni di 'CASCADE'. Per fare ciò basta aggiungere 'ENGINE InnoDB' prima dell'ultimo'; '. – Patrick

7

Penso (non sono sicuro) che i vincoli di chiave esterna non faranno esattamente quello che vuoi dato il tuo progetto di tabella. Forse la cosa migliore da fare è definire una procedura memorizzata che cancelli una categoria nel modo desiderato, quindi chiama quella procedura ogni volta che desideri 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 per la 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 di vincolo può, ovviamente, appaiono anche nell'istruzione CREATE TABLE.

Dopo aver creato questi oggetti schema, è possibile eliminare una categoria e ottenere il comportamento desiderato immettendo CALL DeleteCategory(category_ID) (dove ID_ordine è la categoria da eliminare) e si comporterà come si desidera. Ma non eseguire una normale query DELETE FROM, a meno che non si desideri un comportamento standard (ad esempio, eliminare dalla tabella di collegamento e lasciare solo la tabella products).

+0

Penso di aver scritto la mia domanda nel modo sbagliato. Se elimino una categoria, come faccio a verificare che non elimini i prodotti che sono correlati ad altre categorie. – Cudos

+0

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

9

mi sono confuso con la risposta a questa domanda, così ho creato un banco di prova in MySQL, speriamo che questo aiuta

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