ON DELETE CASCADE
ON DELETE CASCADE es una acción referencial que se define junto con una clave foránea. Cuando eliminas una fila de la tabla padre, MySQL elimina automáticamente todas las filas de la tabla hija que la referencian. Es la forma más práctica de mantener la integridad referencial en relaciones donde las filas hijas no tienen sentido sin su fila padre.
Sintaxis
FOREIGN KEY (columna) REFERENCES tabla_padre(columna_padre)
ON DELETE CASCADEEjemplo práctico
Consideremos un blog con artículos y comentarios. Si eliminamos un artículo, sus comentarios deberían eliminarse automáticamente:
CREATE TABLE articulos (
id INT AUTO_INCREMENT PRIMARY KEY,
titulo VARCHAR(200) NOT NULL,
contenido TEXT
);
CREATE TABLE comentarios (
id INT AUTO_INCREMENT PRIMARY KEY,
articulo_id INT NOT NULL,
autor VARCHAR(100) NOT NULL,
texto TEXT NOT NULL,
CONSTRAINT fk_comentario_articulo
FOREIGN KEY (articulo_id) REFERENCES articulos(id)
ON DELETE CASCADE
);
INSERT INTO articulos VALUES
(1, 'MySQL básico', 'Introducción a MySQL...'),
(2, 'JOINs avanzados', 'Los JOINs permiten...');
INSERT INTO comentarios (articulo_id, autor, texto) VALUES
(1, 'Ana', 'Muy útil, gracias'),
(1, 'Luis', 'Excelente explicación'),
(1, 'Eva', '¿Puedes hacer uno de PostgreSQL?'),
(2, 'Carlos', 'Los diagramas ayudan mucho');Verificamos los datos:
SELECT a.titulo, COUNT(c.id) AS comentarios
FROM articulos a
LEFT JOIN comentarios c ON a.id = c.articulo_id
GROUP BY a.id, a.titulo;| titulo | comentarios |
|---|---|
| MySQL básico | 3 |
| JOINs avanzados | 1 |
Ahora eliminamos el artículo 1:
DELETE FROM articulos WHERE id = 1;Query OK, 1 row affected (0.01 sec)
MySQL elimina automáticamente los 3 comentarios asociados:
SELECT * FROM comentarios;| id | articulo_id | autor | texto |
|---|---|---|---|
| 4 | 2 | Carlos | Los diagramas ayudan mucho |
Solo queda el comentario del artículo 2, que no fue eliminado.
CASCADE en cadena
Si hay múltiples niveles de relaciones con CASCADE, la eliminación se propaga a través de todos ellos:
CREATE TABLE foros (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL
);
CREATE TABLE hilos (
id INT AUTO_INCREMENT PRIMARY KEY,
foro_id INT NOT NULL,
titulo VARCHAR(200) NOT NULL,
FOREIGN KEY (foro_id) REFERENCES foros(id) ON DELETE CASCADE
);
CREATE TABLE respuestas (
id INT AUTO_INCREMENT PRIMARY KEY,
hilo_id INT NOT NULL,
texto TEXT NOT NULL,
FOREIGN KEY (hilo_id) REFERENCES hilos(id) ON DELETE CASCADE
);
INSERT INTO foros VALUES (1, 'MySQL');
INSERT INTO hilos VALUES (1, 1, '¿Cómo hacer un JOIN?');
INSERT INTO respuestas VALUES (1, 1, 'Usa INNER JOIN...'), (2, 1, 'También puedes usar LEFT JOIN...');Al eliminar el foro, se eliminan sus hilos, y al eliminarse los hilos, se eliminan sus respuestas:
DELETE FROM foros WHERE id = 1;
SELECT * FROM hilos; -- Empty set
SELECT * FROM respuestas; -- Empty setLa eliminación se propaga automáticamente por toda la cadena.
CASCADE en tienda_mysql
Nuestra base de datos usa ON DELETE CASCADE en dos relaciones clave:
pedidos.cliente_id referencia clientes.id con ON DELETE CASCADE. Si eliminamos un cliente, se eliminan todos sus pedidos.
detalle_pedidos.pedido_id referencia pedidos.id con ON DELETE CASCADE. Si se elimina un pedido, se eliminan automáticamente sus líneas de detalle.
Esto crea una cadena: eliminar un cliente elimina sus pedidos, y eliminar esos pedidos elimina sus detalles. Tres tablas se limpian con un solo DELETE.
-- Ver cuántos registros tiene Diego (cliente 20)
SELECT
(SELECT COUNT(*) FROM pedidos WHERE cliente_id = 20) AS pedidos,
(SELECT COUNT(*) FROM detalle_pedidos dp
JOIN pedidos p ON dp.pedido_id = p.id
WHERE p.cliente_id = 20) AS detalles;| pedidos | detalles |
|---|---|
| 0 | 0 |
Diego no tiene pedidos, así que un DELETE sobre él no afectaría a otras tablas. Pero si María (con 3 pedidos) fuera eliminada, sus pedidos y todos sus detalles desaparecerían automáticamente.
Comparación: CASCADE vs SET NULL vs RESTRICT
Cada acción tiene un caso de uso diferente:
-- CASCADE: las filas hijas no tienen sentido sin el padre
-- Ejemplo: líneas de detalle sin pedido
FOREIGN KEY (pedido_id) REFERENCES pedidos(id) ON DELETE CASCADE
-- SET NULL: las filas hijas pueden existir sin padre
-- Ejemplo: empleado sin supervisor
FOREIGN KEY (supervisor_id) REFERENCES empleados(id) ON DELETE SET NULL
-- RESTRICT: prohibir la eliminación si hay dependencias
-- Ejemplo: no eliminar un producto que aparece en pedidos
FOREIGN KEY (producto_id) REFERENCES productos(id) ON DELETE RESTRICTCASCADE es apropiado para relaciones de composición, donde la parte no puede existir sin el todo: comentarios de un artículo, líneas de un pedido, archivos de un proyecto.
SET NULL es apropiado para relaciones de asociación, donde la fila hija tiene existencia independiente: un empleado cuyo supervisor es eliminado debería seguir existiendo, solo pierde la referencia.
RESTRICT es apropiado para proteger la integridad histórica: un producto que aparece en pedidos anteriores no debería poder eliminarse, porque destruiría el historial de ventas.
ON UPDATE CASCADE
Además de ON DELETE, puedes definir ON UPDATE CASCADE para propagar cambios en la clave primaria:
CREATE TABLE codigos_producto (
sku CHAR(8) PRIMARY KEY,
nombre VARCHAR(100)
);
CREATE TABLE stock_tienda (
id INT AUTO_INCREMENT PRIMARY KEY,
sku CHAR(8),
cantidad INT,
FOREIGN KEY (sku) REFERENCES codigos_producto(sku)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO codigos_producto VALUES ('PROD-001', 'Laptop');
INSERT INTO stock_tienda (sku, cantidad) VALUES ('PROD-001', 50);
-- Cambiar el SKU propaga el cambio
UPDATE codigos_producto SET sku = 'LAP-0001' WHERE sku = 'PROD-001';
SELECT * FROM stock_tienda;| id | sku | cantidad |
|---|---|---|
| 1 | LAP-0001 | 50 |
En la práctica, ON UPDATE CASCADE se usa poco porque las claves primarias basadas en AUTO_INCREMENT no cambian.
Precauciones
ON DELETE CASCADE puede eliminar más datos de los esperados. Si no eres consciente de las relaciones en cadena, un DELETE sobre una tabla padre puede borrar miles de registros en tablas hijas y nietas.
Antes de usar CASCADE, evalúa si realmente quieres que los datos hijos se eliminen automáticamente. Para datos con valor histórico (como pedidos completados), RESTRICT suele ser más apropiado.
Para ver qué tablas tienen CASCADE configurado:
SELECT
TABLE_NAME,
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME,
DELETE_RULE
FROM information_schema.REFERENTIAL_CONSTRAINTS rc
JOIN information_schema.KEY_COLUMN_USAGE kcu
USING (CONSTRAINT_SCHEMA, CONSTRAINT_NAME)
WHERE rc.CONSTRAINT_SCHEMA = DATABASE()
AND DELETE_RULE = 'CASCADE';Limpieza
DROP TABLE IF EXISTS respuestas;
DROP TABLE IF EXISTS hilos;
DROP TABLE IF EXISTS foros;
DROP TABLE IF EXISTS comentarios;
DROP TABLE IF EXISTS articulos;
DROP TABLE IF EXISTS stock_tienda;
DROP TABLE IF EXISTS codigos_producto;En el siguiente artículo veremos la restricción UNIQUE, que garantiza que los valores de una columna no se repitan.
Escrito por Eduardo Lázaro
