FOREIGN KEY en SQL: qué es, cómo se usa y ejemplos prácticos

Una FOREIGN KEY (clave foránea) es una restricción que vincula una columna (o conjunto de columnas) de una tabla con la PRIMARY KEY (o una UNIQUE) de otra tabla. Su objetivo es garantizar la integridad referencial: que los registros hijos siempre apunten a un registro padre existente, y que no queden huérfanos cuando se borra o modifica el padre.

Gracias a las claves foráneas, el propio motor de base de datos evita inconsistencias lógicas que, de otro modo, tendrías que controlar manualmente desde la aplicación.

Conceptos clave

  • Tabla padre (referenciada): la que expone la clave primaria/única (por ejemplo, clientes(id)).
  • Tabla hija (referenciante): la que contiene la clave foránea (por ejemplo, pedidos(cliente_id)).
  • Integridad referencial: el SGBD impide insertar/actualizar un hijo que apunte a un padre inexistente, y también controla qué ocurre si el padre cambia o se elimina.
  • Acciones ON DELETE / ON UPDATE: reglas que definen el comportamiento ante cambios en el padre (CASCADE, RESTRICT, SET NULL, SET DEFAULT, etc.).

Sintaxis básica (DDL)

Definir FOREIGN KEY al crear la tabla

CREATE TABLE clientes (
  id INT PRIMARY KEY,
  nombre VARCHAR(100) NOT NULL
);

CREATE TABLE pedidos (
  id INT PRIMARY KEY,
  cliente_id INT NOT NULL,
  fecha DATE NOT NULL,
  total DECIMAL(10,2) NOT NULL CHECK (total >= 0),
  CONSTRAINT fk_pedidos_clientes
    FOREIGN KEY (cliente_id)
    REFERENCES clientes(id)
    ON DELETE RESTRICT
    ON UPDATE CASCADE
);
  • FOREIGN KEY (cliente_id) declara la columna referenciante.
  • REFERENCES clientes(id) indica la columna referenciada (debe ser PRIMARY KEY o UNIQUE).
  • ON DELETE y ON UPDATE marcan el comportamiento ante cambios en clientes(id).

Agregar una FK con ALTER TABLE

ALTER TABLE pedidos
ADD CONSTRAINT fk_pedidos_clientes
FOREIGN KEY (cliente_id)
REFERENCES clientes(id)
ON DELETE CASCADE;

Quitar una FK

ALTER TABLE pedidos
DROP CONSTRAINT fk_pedidos_clientes;

(El nombre de la restricción puede variar si el motor lo generó automáticamente.)

Acciones ON DELETE / ON UPDATE

  • RESTRICT (o NO ACTION): impide borrar/actualizar el padre si hay hijos que lo referencian. Es la opción más segura por defecto en muchos motores.
  • CASCADE: al borrar/actualizar el padre, propaga la operación a los hijos (borra o actualiza la FK). Útil, pero requiere cuidado.
  • SET NULL: al borrar/actualizar el padre, la FK del hijo se vuelve NULL (la FK debe permitir NULL).
  • SET DEFAULT: establece un valor por defecto en la FK del hijo (esa columna debe tener DEFAULT válido).
  • NO ACTION: similar a RESTRICT en la mayoría de motores; la comprobación se difiere al final de la sentencia (no confundir con “no hacer nada”).

Consejo: Elegí la acción más alineada con tu negocio. RESTRICT protege; CASCADE automatiza; SET NULL “desengancha” sin borrar.

Ejemplos prácticos

1) Pedidos y clientes (relación 1:N)

CREATE TABLE clientes (
  id INT PRIMARY KEY,
  nombre VARCHAR(100) NOT NULL
);

CREATE TABLE pedidos (
  id INT PRIMARY KEY,
  cliente_id INT NOT NULL,
  fecha DATE NOT NULL,
  total DECIMAL(10,2) NOT NULL,
  CONSTRAINT fk_pedido_cliente
    FOREIGN KEY (cliente_id) REFERENCES clientes(id)
    ON DELETE RESTRICT
    ON UPDATE CASCADE
);
  • No se puede insertar un pedido con cliente_id inexistente.
  • Si el id del cliente cambia (raro, pero posible), se actualiza en pedidos.

2) SET NULL para desasociar

CREATE TABLE productos (
  id INT PRIMARY KEY,
  nombre VARCHAR(120) NOT NULL
);

CREATE TABLE fotos_producto (
  id INT PRIMARY KEY,
  producto_id INT NULL,
  url TEXT NOT NULL,
  CONSTRAINT fk_foto_producto
    FOREIGN KEY (producto_id) REFERENCES productos(id)
    ON DELETE SET NULL
);
  • Si se borra un producto, las fotos no se eliminan: se quedan con producto_id = NULL.

3) Clave foránea compuesta

CREATE TABLE cursos (
  codigo VARCHAR(10),
  version INT,
  titulo VARCHAR(200),
  PRIMARY KEY (codigo, version)
);

CREATE TABLE inscripciones (
  alumno_id INT,
  curso_codigo VARCHAR(10),
  curso_version INT,
  fecha DATE NOT NULL,
  PRIMARY KEY (alumno_id, curso_codigo, curso_version),
  CONSTRAINT fk_insc_curso
    FOREIGN KEY (curso_codigo, curso_version)
    REFERENCES cursos(codigo, version)
    ON DELETE RESTRICT
);
  • La FK referencía dos columnas; deben coincidir en orden y tipos.

4) FK autocontenida (self-reference)

CREATE TABLE empleados (
  id INT PRIMARY KEY,
  nombre VARCHAR(100) NOT NULL,
  jefe_id INT NULL,
  CONSTRAINT fk_empleado_jefe
    FOREIGN KEY (jefe_id) REFERENCES empleados(id)
    ON DELETE SET NULL
);
  • Un empleado puede tener como jefe a otro empleado.
  • Al borrar un jefe, sus subordinados quedan con jefe_id = NULL.

Diferencias entre motores (visión general)

  • PostgreSQL: soporte sólido de FKs, DEFERRABLE INITIALLY DEFERRED/IMMEDIATE (permite aplazar la validación hasta COMMIT).
  • MySQL/MariaDB: FKs reales con InnoDB. RESTRICT, CASCADE, SET NULL. Atención a tipos/longitudes: deben coincidir exactamente (collation/charset incluido en cadenas).
  • SQL Server: FKs con opciones de ON DELETE/UPDATE. Permite WITH NOCHECK (no recomendado en producción) para deshabilitar comprobaciones.
  • Oracle: soporte completo, acciones estándar. Puede diferir validaciones (DEFERRABLE).

Reglas finas (p. ej., restricciones diferibles, nombre máximo de constraint, detalles de NO ACTION) dependen del SGBD: revisá la documentación específica.

Índices y rendimiento

  • Índice en la clave primaria/única de la tabla padre: obligado (PRIMARY/UNIQUE ya crean índice).
  • Índice en la columna FK del hijo: muy recomendado. Acelera:
    • Validaciones al insertar/actualizar.
    • ON DELETE/UPDATE CASCADE.
    • JOINs frecuentes entre padre e hijo.

Ejemplo:

CREATE INDEX ix_pedidos_cliente_id ON pedidos(cliente_id);

Sin ese índice, las operaciones de borrado/actualización del padre pueden disparar escaneos costosos en la tabla hija.

Errores y trampas frecuentes

  1. Tipos incompatibles
    La FK debe tener mismo tipo (y longitud/collation idénticos para texto) que la columna referenciada. Si no, error.
  2. Insertar hijos antes que el padre
    No podés crear un registro hijo que apunte a un padre que aún no existe (salvo restricciones diferibles).
    Solución: inserta primero el padre o usa transacciones con constraints DEFERRABLE (cuando el motor lo soporte).
  3. Borrado masivo con CASCADE inesperado
    Un DELETE en el padre con ON DELETE CASCADE puede borrar miles de hijos.
    Solución: entender el grafo de dependencias y auditar antes de ejecutar.
  4. Falsos huérfanos por SET NULL
    Si la lógica del negocio no contempla hijos “desenganchados”, SET NULL puede generar filas inútiles.
    Solución: preferir RESTRICT o CASCADE según el caso.
  5. Deshabilitar FKs “por performance”
    Desactivar validaciones para cargas masivas puede introducir datos corruptos que luego cuestan más. Si lo hacés, validá después.
  6. Circularidad de referencias
    Tablas que se referencian mutuamente complican el orden de inserción. Soluciones:
  • Permitir NULL temporalmente en la FK.
  • Usar restricciones diferibles.
  • Insertar en varias fases.
  1. Migraciones sin nombre de constraint
    Dejar que el motor nombre la FK puede dificultar alter/drop posteriores. Mejor nombrarlas siempre.

Buenas prácticas

  • Nombrado consistente: fk_<tabla_hija>_<tabla_padre>.
  • Índices en las FKs del lado hijo.
  • Elegir la política ON DELETE/UPDATE acorde al negocio (no “a ciegas”).
  • Evitar FKs sobre columnas volátiles (cambian con frecuencia).
  • Usar transacciones para operaciones masivas que afecten padre/hijos.
  • Documentar el diagrama (ERD) y el sentido de cada relación.
  • Pruebas de integridad: queries de auditoría para detectar huérfanos (si se deshabilitaron FKs temporalmente).

Auditoría: detectar huérfanos (cuando no hay FK o se desactivó)

SELECT p.id, p.cliente_id
FROM pedidos p
LEFT JOIN clientes c ON c.id = p.cliente_id
WHERE c.id IS NULL;

Si retorna filas, tenés pedidos que apuntan a clientes inexistentes: deberían corregirse o definirse FKs para prevenirlo.

Preguntas frecuentes (FAQ)

1) ¿La columna referenciada debe ser PRIMARY KEY?
Puede ser PRIMARY KEY o cualquier UNIQUE. Lo importante es que sea única y no nula.

2) ¿Se puede tener una FK a la misma tabla?
Sí (self-reference), típico para jerarquías como empleados/jefes o categorías/subcategorías.

3) ¿Puedo tener FKs compuestas?
Sí. Deben coincidir todas las columnas (orden y tipos) con la clave compuesta referenciada.

4) ¿Qué diferencia hay entre RESTRICT y NO ACTION?
En la práctica, suelen comportarse igual (bloquean la operación si hay hijos). Detalles finos dependen del SGBD.

5) ¿Conviene CASCADE?
Depende del negocio. CASCADE reduce código pero puede borrar en cadena. Si la eliminación física no es deseable, considerá soft delete (columna activo/eliminado_en) y mantené RESTRICT.

6) ¿Las FKs afectan la performance?
Sí, validan en cada INSERT/UPDATE/DELETE. Con índices adecuados, el impacto es bajo y la calidad de datos compensa con creces.

Conclusión

Las FOREIGN KEY son la base de la integridad referencial en modelos relacionales. Definirlas correctamente evita datos huérfanos, mantiene coherencia entre tablas y simplifica la lógica en la capa de aplicación.
Elegir bien las acciones ON DELETE/UPDATE, indexar las columnas involucradas y nombrar las restricciones de forma consistente son claves para un esquema robusto y mantenible.

Entiende llaves foráneas y referencias en Claves y restricciones y consulta el Glosario SQL completo.

Scroll al inicio