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_NAMECOLUMN_NAMEREFERENCED_TABLE_NAMEREFERENCED_COLUMN_NAME
categoriascategoria_padre_idcategoriasid
detalle_pedidospedido_idpedidosid
detalle_pedidosproducto_idproductosid
empleadossupervisor_idempleadosid
etiquetas_productoproducto_idproductosid
pedidoscliente_idclientesid
pedidosempleado_idempleadosid
productoscategoria_idcategoriasid
resenascliente_idclientesid
resenasproducto_idproductosid

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;
idtituloresponsable_id
1Revisar códigoNULL

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;
idcodigo_ref
1XYZ

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;
nombrepuestosupervisor
RicardoDirector GeneralNULL
SofíaDirectora de VentasRicardo
AlbertoDirector de LogísticaRicardo
NataliaVendedora SeniorSofía
DanielVendedorSofía
PatriciaVendedoraSofía
RaúlVendedor JuniorNatalia
CristinaResponsable de AlmacénAlberto
MarcosOperario de AlmacénCristina
InésOperaria de AlmacénCristina

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