Restricciones de clave externa de MySQL, eliminación en cascada
Quiero usar claves externas para mantener la integridad y evitar huérfanos (ya uso innoDB).
¿Cómo hago una declaración SQL que se ELIMINA EN CASCADA?
Si elimino una categoría, ¿cómo me aseguro de que no elimine productos que también estén relacionados con otras categorías?
La tabla dinámica "categorías_productos" crea una relación de muchos a muchos entre las otras dos tablas.
categories
- id (INT)
- name (VARCHAR 255)
products
- id
- name
- price
categories_products
- categories_id
- products_id
Si su eliminación en cascada destruye un producto porque era miembro de una categoría que fue eliminada, entonces ha configurado sus claves externas incorrectamente. Dadas sus tablas de ejemplo, debería tener la siguiente configuración de tabla:
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;
De esta manera, puede eliminar un producto O una categoría, y solo los registros asociados en categorías_productos desaparecerán al mismo tiempo. La cascada no subirá más en el árbol y eliminará la tabla principal de producto/categoría.
p.ej
products: boots, mittens, hats, coats
categories: red, green, blue, white, black
prod/cats: red boots, green mittens, red coats, black hats
Si elimina la categoría 'roja', entonces solo muere la entrada 'roja' en la tabla de categorías, así como las dos entradas prod/cats: 'botas rojas' y 'abrigos rojos'.
La eliminación no seguirá en cascada y no eliminará las categorías "botas" y "abrigos".
seguimiento de comentarios:
todavía no entiendes cómo funcionan las eliminaciones en cascada. Sólo afectan a las tablas en las que está definida la "cascada al eliminar". En este caso, la cascada se establece en la tabla "categorías_productos". Si elimina la categoría 'roja', los únicos registros que se eliminarán en cascada en categorías_productos son aquellos donde category_id = red
. No tocará ningún registro donde 'category_id = blue' y no avanzará a la tabla "productos", porque no hay ninguna clave externa definida en esa tabla.
Aquí hay un ejemplo más 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
+------------+-------------+
Digamos que eliminas la categoría #2 (azul):
DELETE FROM categories WHERE (id = 2);
el DBMS mirará todas las tablas que tienen una clave externa apuntando a la tabla 'categorías' y eliminará los registros donde la identificación coincidente es 2. Dado que solo definimos la relación de clave externa en products_categories
, terminará con esta tabla una vez que eliminar completa:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1 | 1 | // red mittens
| 2 | 1 | // red boots
+------------+-------------+
No hay ninguna clave externa definida en la products
tabla, por lo que la cascada no funcionará allí, por lo que aún tienes botas y guantes en la lista. Ya no existen las 'botas azules' ni los 'guantes azules'.
La respuesta a esta pregunta me confundió, así que creé un caso de prueba en MySQL, espero que esto ayude
-- 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
Creo (no estoy seguro) que las restricciones de clave externa no harán exactamente lo que usted desea, dado el diseño de su tabla. Quizás lo mejor que puede hacer es definir un procedimiento almacenado que elimine una categoría de la manera que desee y luego llamar a ese procedimiento cada vez que desee eliminar una categoría.
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
También debe agregar las siguientes restricciones de clave externa a la tabla de vinculación:
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
Por supuesto, la cláusula CONSTRAINT también puede aparecer en la declaración CREATE TABLE.
Una vez creados estos objetos de esquema, puede eliminar una categoría y obtener el comportamiento que desea emitiendo CALL DeleteCategory(category_ID)
(donde categoría_ID es la categoría que se eliminará), y se comportará como desee. Pero no emita una DELETE FROM
consulta normal, a menos que desee un comportamiento más estándar (es decir, eliminar sólo de la tabla de enlace y dejar la products
tabla en paz).