Cuando diseñas una base de datos relacional, dos de los conceptos más fundamentales son la clave primaria y la clave foránea. Son las herramientas que garantizan que los datos estén bien organizados, que cada registro sea único, y que las relaciones entre tablas sean coherentes. Sin ellas, una base de datos sería simplemente un conjunto de hojas de cálculo desconectadas.
En esta guía vas a entender qué es cada una, cuáles son sus diferencias, cómo crearlas en MySQL y PostgreSQL, y qué pasa cuando intentas violar las reglas que imponen.
Qué es una clave primaria
Una clave primaria (PRIMARY KEY) es una columna o combinación de columnas que identifica de forma única cada fila de una tabla. Ningún valor puede repetirse y ningún valor puede ser nulo. Cada tabla solo puede tener una clave primaria.
Piensa en ella como el DNI de una persona: no hay dos personas con el mismo DNI, y todo el mundo tiene uno. En una base de datos, la clave primaria es el "DNI" de cada fila.
-- Tabla de clientes con clave primaria en la columna 'id'
CREATE TABLE clientes (
id INT PRIMARY KEY AUTO_INCREMENT, -- MySQL
nombre VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL
);
-- En PostgreSQL, se usa SERIAL o GENERATED ALWAYS AS IDENTITY
CREATE TABLE clientes (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL
);
Cuando defines una columna como PRIMARY KEY, el motor de base de datos automáticamente:
- Crea un índice único sobre esa columna (las búsquedas por clave primaria son instantáneas)
- Rechaza cualquier intento de insertar un valor duplicado
- Rechaza cualquier intento de insertar o actualizar a NULL
INSERT INTO clientes (nombre, email) VALUES ('Ana García', 'ana@mail.com');
INSERT INTO clientes (nombre, email) VALUES ('Carlos López', 'carlos@mail.com');
-- Intentar insertar con id duplicado falla:
INSERT INTO clientes (id, nombre, email) VALUES (1, 'Pedro', 'pedro@mail.com');
-- ERROR: Duplicate entry '1' for key 'PRIMARY'
-- Con AUTO_INCREMENT/IDENTITY, el motor asigna el id automáticamente
-- y nunca repite valores, aunque se hayan borrado filas anteriores
Tipos de claves primarias
Clave primaria simple: una sola columna
La más habitual. Un único campo identifica cada fila.
CREATE TABLE productos (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
precio DECIMAL(10, 2) NOT NULL
);
CREATE TABLE empleados (
dni CHAR(9) PRIMARY KEY, -- el DNI ya es único por naturaleza
nombre VARCHAR(100) NOT NULL,
salario DECIMAL(10, 2)
);
Clave primaria compuesta: varias columnas juntas
A veces ninguna columna por sí sola identifica un registro de forma única, pero la combinación de varias sí lo hace. En ese caso se usa una clave primaria compuesta.
-- Un alumno puede matricularse en varias asignaturas
-- Una asignatura puede tener varios alumnos
-- La combinación alumno_id + asignatura_id identifica cada matrícula de forma única
CREATE TABLE matriculas (
alumno_id INT,
asignatura_id INT,
fecha DATE NOT NULL,
nota DECIMAL(4, 2),
PRIMARY KEY (alumno_id, asignatura_id) -- clave primaria compuesta
);
-- Esto es válido: el mismo alumno en asignaturas distintas
INSERT INTO matriculas VALUES (1, 101, '2026-09-01', NULL);
INSERT INTO matriculas VALUES (1, 102, '2026-09-01', NULL);
-- Esto falla: el mismo alumno ya está matriculado en esa asignatura
INSERT INTO matriculas VALUES (1, 101, '2026-09-01', NULL);
-- ERROR: Duplicate entry '1-101' for key 'PRIMARY'
Claves naturales vs claves artificiales (surrogate keys)
Una clave natural es un valor que ya existe en el mundo real y que es único por su propia naturaleza: el DNI, el ISBN de un libro, el código ISIN de una acción. Una clave artificial o surrogate key es un identificador creado específicamente para la base de datos, sin significado en el mundo real: un número entero autoincremental o un UUID.
-- Clave natural: el ISBN ya identifica un libro de forma única en el mundo real
CREATE TABLE libros (
isbn CHAR(13) PRIMARY KEY,
titulo VARCHAR(200) NOT NULL,
autor VARCHAR(100)
);
-- Clave artificial: el id no tiene significado propio, solo sirve para identificar
CREATE TABLE libros (
id INT PRIMARY KEY AUTO_INCREMENT,
isbn CHAR(13) UNIQUE NOT NULL,
titulo VARCHAR(200) NOT NULL,
autor VARCHAR(100)
);
En la práctica, las claves artificiales son más habituales porque son estables (un DNI puede cambiar si alguien se naturaliza, un ISBN puede tener erratas), más eficientes para los joins (un entero ocupa menos que un texto largo) y no exponen información real de negocio.
Qué es una clave foránea
Una clave foránea (FOREIGN KEY) es una columna en una tabla que hace referencia a la clave primaria de otra tabla (o de la misma tabla). Establece y hace cumplir la relación entre las dos tablas.
Si la clave primaria es el "DNI" de cada fila, la clave foránea es como decir "esta fila pertenece a la persona con este DNI". Garantiza que no puedes referenciar algo que no existe: la integridad referencial.
-- La tabla 'pedidos' hace referencia a la tabla 'clientes'
CREATE TABLE pedidos (
id INT PRIMARY KEY AUTO_INCREMENT,
cliente_id INT NOT NULL,
total DECIMAL(10, 2) NOT NULL,
fecha DATE NOT NULL,
FOREIGN KEY (cliente_id) REFERENCES clientes(id)
);
Con esta clave foránea, el motor garantiza:
- No puedes insertar un pedido con un
cliente_idque no existe en la tablaclientes - No puedes eliminar un cliente que tiene pedidos (por defecto, el motor lo rechaza)
- No puedes cambiar el
idde un cliente si tiene pedidos que lo referencian
-- Esto funciona: el cliente 1 existe
INSERT INTO pedidos (cliente_id, total, fecha) VALUES (1, 59.99, '2026-06-15');
-- Esto falla: no existe ningún cliente con id = 999
INSERT INTO pedidos (cliente_id, total, fecha) VALUES (999, 29.99, '2026-06-15');
-- ERROR: Cannot add or update a child row: a foreign key constraint fails
-- (pedidos, CONSTRAINT pedidos_ibfk_1 FOREIGN KEY (cliente_id) REFERENCES clientes (id))
Un esquema completo con relaciones
-- Primero las tablas sin dependencias
CREATE TABLE categorias (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE clientes (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
telefono VARCHAR(20)
);
-- Luego las tablas que dependen de las anteriores
CREATE TABLE productos (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
precio DECIMAL(10, 2) NOT NULL,
stock INT DEFAULT 0,
categoria_id INT,
FOREIGN KEY (categoria_id) REFERENCES categorias(id)
);
CREATE TABLE pedidos (
id INT PRIMARY KEY AUTO_INCREMENT,
cliente_id INT NOT NULL,
fecha DATETIME DEFAULT CURRENT_TIMESTAMP,
estado VARCHAR(20) DEFAULT 'pendiente',
FOREIGN KEY (cliente_id) REFERENCES clientes(id)
);
-- Tabla intermedia: un pedido puede tener varios productos
CREATE TABLE pedido_lineas (
pedido_id INT NOT NULL,
producto_id INT NOT NULL,
cantidad INT NOT NULL,
precio_unit DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (pedido_id, producto_id), -- clave primaria compuesta
FOREIGN KEY (pedido_id) REFERENCES pedidos(id),
FOREIGN KEY (producto_id) REFERENCES productos(id)
);
Hay que crear las tablas en el orden correcto: primero las que no tienen claves foráneas (o cuyas referencias ya existen), y luego las que dependen de ellas. Si intentas crear pedidos antes que clientes, el motor dará error porque la tabla referenciada no existe todavía.
ON DELETE y ON UPDATE: qué pasa cuando cambia el registro referenciado
Cuando intentas eliminar o actualizar el registro padre (el que tiene la clave primaria referenciada), la base de datos necesita saber qué hacer con los registros hijos (los que tienen la clave foránea). Esto se configura con las opciones ON DELETE y ON UPDATE.
-- Las opciones disponibles:
-- RESTRICT (comportamiento por defecto): rechaza la operación con error
FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE RESTRICT
-- NO ACTION: igual que RESTRICT en la práctica en MySQL y PostgreSQL
FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE NO ACTION
-- CASCADE: si se elimina/actualiza el padre, se eliminan/actualizan los hijos automáticamente
FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE CASCADE
-- SET NULL: si se elimina/actualiza el padre, los hijos quedan con NULL en la clave foránea
-- (la columna debe permitir NULL)
FOREIGN KEY (categoria_id) REFERENCES categorias(id) ON DELETE SET NULL
-- SET DEFAULT: los hijos quedan con el valor por defecto de la columna
-- (poco soportado, prácticamente no se usa)
FOREIGN KEY (categoria_id) REFERENCES categorias(id) ON DELETE SET DEFAULT
-- Ejemplo real: qué hacer cuando se elimina un cliente
CREATE TABLE pedidos (
id INT PRIMARY KEY AUTO_INCREMENT,
cliente_id INT,
total DECIMAL(10, 2) NOT NULL,
fecha DATE NOT NULL,
-- Opción 1: no permitir borrar un cliente con pedidos (más seguro para datos financieros)
FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE RESTRICT,
-- Opción 2: borrar también sus pedidos (útil en cuentas de usuario que se cierran)
-- FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE CASCADE,
-- Opción 3: conservar el pedido pero desvincular del cliente (para históricos)
-- FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE SET NULL
);
-- Probar CASCADE en la práctica
CREATE TABLE clientes (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100)
);
CREATE TABLE pedidos (
id INT PRIMARY KEY AUTO_INCREMENT,
cliente_id INT,
total DECIMAL(10,2),
FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE CASCADE
);
INSERT INTO clientes (nombre) VALUES ('Ana García'); -- id = 1
INSERT INTO pedidos (cliente_id, total) VALUES (1, 100);
INSERT INTO pedidos (cliente_id, total) VALUES (1, 200);
SELECT COUNT(*) FROM pedidos WHERE cliente_id = 1; -- 2 pedidos
-- Al eliminar el cliente, sus pedidos se borran automáticamente
DELETE FROM clientes WHERE id = 1;
SELECT COUNT(*) FROM pedidos WHERE cliente_id = 1; -- 0 pedidos (borrados en cascada)
Diferencias clave resumidas
| Característica | Clave primaria | Clave foránea |
|---|---|---|
| Propósito | Identificar de forma única cada fila | Establecer la relación entre dos tablas |
| Valores nulos | No permitidos (NOT NULL implícito) | Permitidos (si la columna lo permite) |
| Valores duplicados | No permitidos | Permitidos (varios hijos pueden referenciar el mismo padre) |
| Cuántas por tabla | Solo una | Las que sean necesarias |
| Crea índice automático | Sí, siempre | En MySQL sí; en PostgreSQL no (recomendable crearlo manualmente) |
| Referencia a | A nada (es el origen) | A la clave primaria de otra tabla |
| Tabla | La propia tabla | Tabla padre (puede ser la misma tabla: autorreferencia) |
La clave foránea puede ser nula
Una clave foránea puede aceptar valores NULL, lo que indica "sin referencia". Esto tiene sentido en relaciones opcionales: un producto puede o no tener categoría, un empleado puede o no tener manager.
CREATE TABLE empleados (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
salario DECIMAL(10, 2),
manager_id INT NULL, -- autorreferencia: el jefe también es un empleado
FOREIGN KEY (manager_id) REFERENCES empleados(id)
);
-- El CEO no tiene manager: manager_id es NULL
INSERT INTO empleados (nombre, salario, manager_id) VALUES ('CEO', 100000, NULL);
-- El director de ingeniería tiene como manager al CEO (id = 1)
INSERT INTO empleados (nombre, salario, manager_id) VALUES ('Dir. Ingeniería', 80000, 1);
-- Un desarrollador con el director como manager (id = 2)
INSERT INTO empleados (nombre, salario, manager_id) VALUES ('Desarrollador', 55000, 2);
-- Ver la jerarquía
SELECT e.nombre, m.nombre AS manager
FROM empleados e
LEFT JOIN empleados m ON m.id = e.manager_id;
-- nombre manager
-- CEO NULL
-- Dir. Ingeniería CEO
-- Desarrollador Dir. Ingeniería
Cómo ver las claves existentes en una base de datos
-- MySQL: ver todas las claves foráneas de una tabla
SELECT
CONSTRAINT_NAME,
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'pedidos'
AND REFERENCED_TABLE_NAME IS NOT NULL;
-- MySQL: ver la definición completa de una tabla (incluye PK y FK)
SHOW CREATE TABLE pedidos;
-- PostgreSQL: ver claves foráneas de una tabla
SELECT
tc.constraint_name,
kcu.column_name,
ccu.table_name AS tabla_referenciada,
ccu.column_name AS columna_referenciada
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON tc.constraint_name = ccu.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name = 'pedidos';
Añadir y eliminar claves en tablas existentes
-- Añadir una clave foránea a una tabla que ya existe
ALTER TABLE pedidos
ADD CONSTRAINT fk_pedidos_cliente
FOREIGN KEY (cliente_id) REFERENCES clientes(id);
-- Eliminar una clave foránea
ALTER TABLE pedidos
DROP FOREIGN KEY fk_pedidos_cliente; -- MySQL
ALTER TABLE pedidos
DROP CONSTRAINT fk_pedidos_cliente; -- PostgreSQL
-- Antes de crear una FK, asegurarse de que los datos existentes son coherentes:
-- todos los valores en cliente_id deben existir en clientes.id
-- o ser NULL si la columna lo permite
-- Verificar datos incoherentes antes de añadir la FK
SELECT DISTINCT p.cliente_id
FROM pedidos p
LEFT JOIN clientes c ON c.id = p.cliente_id
WHERE c.id IS NULL AND p.cliente_id IS NOT NULL;
-- Si esta consulta devuelve filas, hay datos huérfanos que hay que limpiar primero
Un error muy común: olvidar el índice en la clave foránea en PostgreSQL
-- En MySQL, la clave foránea crea automáticamente un índice en la columna
-- En PostgreSQL NO lo hace: hay que crearlo manualmente
-- Sin índice en la FK, los JOINs y las comprobaciones de integridad son lentos
CREATE TABLE pedidos (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
cliente_id INT REFERENCES clientes(id)
);
-- ✅ Crear el índice manualmente en PostgreSQL
CREATE INDEX idx_pedidos_cliente_id ON pedidos(cliente_id);
-- Esto es especialmente importante en tablas grandes:
-- cada INSERT, UPDATE y DELETE que afecte a la tabla padre
-- necesita comprobar la integridad referencial en la tabla hija
-- y sin índice esa comprobación hace un full table scan
Resumen
- La clave primaria identifica de forma única cada fila de una tabla. No puede ser NULL ni duplicada, y cada tabla solo puede tener una.
- La clave foránea establece la relación entre dos tablas referenciando la clave primaria de otra. Garantiza que no puedes referenciar registros que no existen (integridad referencial).
- Las claves primarias pueden ser simples (una columna) o compuestas (varias columnas juntas). Las artificiales (autoincrement, UUID) son más habituales y estables que las naturales.
- Las claves foráneas pueden ser NULL (relación opcional) y pueden aparecer múltiples veces en la misma tabla.
ON DELETE CASCADEborra los hijos cuando se borra el padre.ON DELETE SET NULLlos desvincula.ON DELETE RESTRICT(por defecto) impide borrar el padre si tiene hijos.- En PostgreSQL, las claves foráneas no crean índices automáticamente: créalos manualmente para que los JOINs y las comprobaciones de integridad sean eficientes.
- Al crear tablas con relaciones, el orden importa: primero las tablas referenciadas, luego las que las referencian.
No hay comentarios todavía. Sé el primero en compartir tu opinión.