Restricciones de clave externa de MySQL, eliminación en cascada

Resuelto Cudos asked hace 14 años • 3 respuestas

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
Cudos avatar May 26 '10 23:05 Cudos
Aceptado

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 productstabla, 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'.

Marc B avatar May 26 '2010 22:05 Marc B

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
Abderrahim avatar Apr 24 '2014 09:04 Abderrahim

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 FROMconsulta normal, a menos que desee un comportamiento más estándar (es decir, eliminar sólo de la tabla de enlace y dejar la productstabla en paz).

Hammerite avatar May 26 '2010 20:05 Hammerite