FOREIGN KEY
La restricción FOREIGN KEY (clave foránea) establece una relación entre dos tablas. Garantiza que los valores de una columna en la tabla hija coincidan con valores existentes en la columna referenciada de la tabla padre. Esto se conoce como integridad referencial: no puedes insertar un pedido que referencie un cliente inexistente, ni eliminar un cliente que tenga pedidos asociados.
Sintaxis
CREATE TABLE tabla_hija (
columna tipo_de_dato,
FOREIGN KEY (columna) REFERENCES tabla_padre(columna_padre)
);Con nombre y acciones referenciales:
CREATE TABLE tabla_hija (
columna tipo_de_dato,
CONSTRAINT nombre_fk FOREIGN KEY (columna)
REFERENCES tabla_padre(columna_padre)
ON DELETE accion
ON UPDATE accion
);Ejemplo básico
CREATE TABLE departamentos (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL
);
CREATE TABLE empleados_fk (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
departamento_id INT,
FOREIGN KEY (departamento_id) REFERENCES departamentos(id)
);INSERT INTO departamentos (nombre) VALUES ('Ventas'), ('Logística'), ('Marketing');Ahora, empleados_fk.departamento_id solo puede contener valores que existan en departamentos.id:
-- Funciona: departamento 1 (Ventas) existe
INSERT INTO empleados_fk (nombre, departamento_id) VALUES ('Ana', 1);
-- Funciona: NULL está permitido (sin departamento asignado)
INSERT INTO empleados_fk (nombre, departamento_id) VALUES ('Luis', NULL);
-- Falla: departamento 99 no existe
INSERT INTO empleados_fk (nombre, departamento_id) VALUES ('Eva', 99);ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails
(`tienda_mysql`.`empleados_fk`, CONSTRAINT `empleados_fk_ibfk_1` FOREIGN KEY
(`departamento_id`) REFERENCES `departamentos` (`id`))
Claves foráneas en tienda_mysql
Nuestra base de datos tiene varias claves foráneas. Veamos las más importantes:
SELECT
TABLE_NAME,
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'tienda_mysql'
AND REFERENCED_TABLE_NAME IS NOT NULL
ORDER BY TABLE_NAME;| TABLE_NAME | COLUMN_NAME | REFERENCED_TABLE_NAME | REFERENCED_COLUMN_NAME |
|---|---|---|---|
| categorias | categoria_padre_id | categorias | id |
| detalle_pedidos | pedido_id | pedidos | id |
| detalle_pedidos | producto_id | productos | id |
| empleados | supervisor_id | empleados | id |
| etiquetas_producto | producto_id | productos | id |
| pedidos | cliente_id | clientes | id |
| pedidos | empleado_id | empleados | id |
| productos | categoria_id | categorias | id |
| resenas | cliente_id | clientes | id |
| resenas | producto_id | productos | id |
categorias.categoria_padre_id es un auto-referencia (referencia a la misma tabla), lo que permite crear la jerarquía de categorías. empleados.supervisor_id es otra auto-referencia para la jerarquía de empleados.
Nombrar claves foráneas
Es recomendable dar nombres descriptivos a las claves foráneas:
CREATE TABLE proyectos (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
departamento_id INT,
responsable_id INT,
CONSTRAINT fk_proyecto_departamento
FOREIGN KEY (departamento_id) REFERENCES departamentos(id),
CONSTRAINT fk_proyecto_responsable
FOREIGN KEY (responsable_id) REFERENCES empleados_fk(id)
);Los nombres descriptivos facilitan diagnosticar errores. Cuando MySQL reporta una violación de clave foránea, incluye el nombre de la restricción en el mensaje de error.
ON DELETE
La cláusula ON DELETE define qué sucede con las filas hijas cuando se elimina la fila padre:
CASCADE elimina automáticamente las filas hijas:
CREATE TABLE pedidos_demo (
id INT AUTO_INCREMENT PRIMARY KEY,
cliente VARCHAR(100)
);
CREATE TABLE items_demo (
id INT AUTO_INCREMENT PRIMARY KEY,
pedido_id INT,
producto VARCHAR(100),
CONSTRAINT fk_item_pedido FOREIGN KEY (pedido_id)
REFERENCES pedidos_demo(id) ON DELETE CASCADE
);
INSERT INTO pedidos_demo VALUES (1, 'María');
INSERT INTO items_demo VALUES (1, 1, 'Laptop'), (2, 1, 'Mouse');
DELETE FROM pedidos_demo WHERE id = 1;
-- Los items se eliminan automáticamente
SELECT * FROM items_demo;Empty set (0.00 sec)
SET NULL establece la clave foránea a NULL:
CREATE TABLE tareas (
id INT AUTO_INCREMENT PRIMARY KEY,
titulo VARCHAR(200),
responsable_id INT,
CONSTRAINT fk_tarea_resp FOREIGN KEY (responsable_id)
REFERENCES empleados_fk(id) ON DELETE SET NULL
);
INSERT INTO tareas VALUES (1, 'Revisar código', 1);
DELETE FROM empleados_fk WHERE id = 1;
SELECT * FROM tareas;| id | titulo | responsable_id |
|---|---|---|
| 1 | Revisar código | NULL |
La tarea se mantiene, pero pierde la referencia al responsable eliminado.
RESTRICT (comportamiento por defecto) impide la eliminación:
-- Con RESTRICT o sin ON DELETE (es lo mismo)
DELETE FROM departamentos WHERE id = 2;Si hay empleados en ese departamento, la eliminación falla. Esto protege contra eliminaciones accidentales.
SET DEFAULT no está soportado en InnoDB (el motor por defecto de MySQL).
ON UPDATE
La cláusula ON UPDATE define qué sucede cuando cambia el valor de la clave primaria de la tabla padre:
CREATE TABLE codigos (
codigo CHAR(3) PRIMARY KEY,
nombre VARCHAR(100)
);
CREATE TABLE referencias (
id INT AUTO_INCREMENT PRIMARY KEY,
codigo_ref CHAR(3),
CONSTRAINT fk_ref FOREIGN KEY (codigo_ref)
REFERENCES codigos(codigo) ON UPDATE CASCADE
);
INSERT INTO codigos VALUES ('ABC', 'Primero');
INSERT INTO referencias VALUES (1, 'ABC');
-- Cambiar la clave primaria
UPDATE codigos SET codigo = 'XYZ' WHERE codigo = 'ABC';
-- La referencia se actualiza automáticamente
SELECT * FROM referencias;| id | codigo_ref |
|---|---|
| 1 | XYZ |
En la práctica, ON UPDATE CASCADE se usa poco porque las claves primarias no deberían cambiar (especialmente con AUTO_INCREMENT).
Claves foráneas compuestas
Si la tabla padre tiene una clave primaria compuesta, la clave foránea también debe ser compuesta:
CREATE TABLE cursos (
departamento_id INT,
numero INT,
nombre VARCHAR(100),
PRIMARY KEY (departamento_id, numero)
);
CREATE TABLE matriculas (
id INT AUTO_INCREMENT PRIMARY KEY,
estudiante VARCHAR(100),
dept_id INT,
curso_num INT,
CONSTRAINT fk_matricula_curso
FOREIGN KEY (dept_id, curso_num)
REFERENCES cursos(departamento_id, numero)
);Auto-referencia
Una tabla puede tener una clave foránea que apunte a sí misma. Nuestra tabla empleados lo usa para la jerarquía de supervisores:
-- La columna supervisor_id referencia a empleados.id
SELECT e.nombre, e.puesto, s.nombre AS supervisor
FROM empleados e
LEFT JOIN empleados s ON e.supervisor_id = s.id;| nombre | puesto | supervisor |
|---|---|---|
| Ricardo | Director General | NULL |
| Sofía | Directora de Ventas | Ricardo |
| Alberto | Director de Logística | Ricardo |
| Natalia | Vendedora Senior | Sofía |
| Daniel | Vendedor | Sofía |
| Patricia | Vendedora | Sofía |
| Raúl | Vendedor Junior | Natalia |
| Cristina | Responsable de Almacén | Alberto |
| Marcos | Operario de Almacén | Cristina |
| Inés | Operaria de Almacén | Cristina |
Añadir clave foránea a tabla existente
ALTER TABLE tabla_hija
ADD CONSTRAINT nombre_fk
FOREIGN KEY (columna) REFERENCES tabla_padre(columna_padre)
ON DELETE CASCADE;Si los datos existentes violan la restricción (hay valores en la columna que no existen en la tabla padre), la operación falla. Primero debes limpiar los datos inconsistentes.
Eliminar clave foránea
ALTER TABLE tabla_hija
DROP FOREIGN KEY nombre_fk;Esto elimina la restricción pero mantiene la columna y su índice. Para eliminar también el índice:
ALTER TABLE tabla_hija
DROP INDEX nombre_fk;Requisitos
La columna referenciada en la tabla padre debe tener un índice (normalmente es la clave primaria). Los tipos de datos de ambas columnas deben coincidir exactamente. Ambas tablas deben usar el motor InnoDB. La tabla padre debe existir antes de crear la clave foránea.
Desactivar temporalmente
En scripts de importación masiva o migraciones, puedes desactivar la verificación de claves foráneas:
SET FOREIGN_KEY_CHECKS = 0;
-- Importar datos en cualquier orden...
SET FOREIGN_KEY_CHECKS = 1;Esto permite insertar datos en la tabla hija antes que en la tabla padre. Asegúrate de reactivar la verificación después y de que los datos sean consistentes.
Limpieza
DROP TABLE IF EXISTS matriculas;
DROP TABLE IF EXISTS cursos;
DROP TABLE IF EXISTS referencias;
DROP TABLE IF EXISTS codigos;
DROP TABLE IF EXISTS tareas;
DROP TABLE IF EXISTS items_demo;
DROP TABLE IF EXISTS pedidos_demo;
DROP TABLE IF EXISTS proyectos;
DROP TABLE IF EXISTS empleados_fk;
DROP TABLE IF EXISTS departamentos;En el siguiente artículo profundizaremos en ON DELETE CASCADE y sus diferentes variantes.
Escrito por Eduardo Lázaro
