Saltar al contenido
Bases de datos con SQL
Todos los cursos Ir al ejercicio →

Relaciones y JOIN

  • ⏱ 60 minutos
  • 📶 Nivel: principiante
  • 🧩 Incluye ejercicio

💡 🎯 Al terminar esta lección podrás…

  • Crear claves foráneas con REFERENCES y entender la integridad referencial.
  • Distinguir relaciones uno a muchos y muchos a muchos.
  • Unir tablas con INNER JOIN y LEFT JOIN.
  • Encontrar filas sin relación con LEFT JOIN … IS NULL.

1.Por qué separar los datos en varias tablas

En la lección 4 viste la regla «un dato, un lugar». Si cada pedido repitiera el nombre y el correo del cliente, un cambio de correo obligaría a tocar muchas filas. La solución es guardar al cliente una sola vez y, en el pedido, solo su id. Ese id escrito en otra tabla se llama clave foránea.

Usaremos cuatro tablas. productos ya la conoces; las otras tres son nuevas (clientes es una versión simplificada de la de la lección 4):

clientestienepedidoscontienelineas_pedidoapunta aproductos

📌 Nota · Los datos de ejemplo

En esta lección y en las siguientes hay 4 clientes (Ana, Luis, Marta y Diego), 4 pedidos y 7 líneas de pedido. Diego todavía no ha comprado nada: lo usaremos para ver qué hace cada tipo de JOIN.

2.Claves foráneas: REFERENCES

Una clave foránea es una columna cuyo valor debe existir como clave primaria en otra tabla. Se declara con REFERENCES:

Tablas relacionadas
CREATE TABLE clientes (
  id     INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL,
  email  TEXT NOT NULL UNIQUE
);

CREATE TABLE pedidos (
  id         INTEGER PRIMARY KEY,
  cliente_id INTEGER NOT NULL REFERENCES clientes(id),
  fecha      TEXT    NOT NULL
);

CREATE TABLE lineas_pedido (
  pedido_id   INTEGER NOT NULL REFERENCES pedidos(id),
  producto_id INTEGER NOT NULL REFERENCES productos(id),
  cantidad    INTEGER NOT NULL CHECK (cantidad > 0),
  PRIMARY KEY (pedido_id, producto_id)
);

Lee cliente_id INTEGER NOT NULL REFERENCES clientes(id) como: «cada pedido pertenece a un cliente que debe existir en clientes». Con esto la base de datos protege la integridad: no deja crear un pedido para un cliente inexistente.

Intento de pedido para un cliente que no existe
INSERT INTO pedidos (cliente_id, fecha)
VALUES (99, '2026-10-05');

Resultado esperado (error real de SQLite):

Error: FOREIGN KEY constraint failed

Lo mismo ocurre al revés: no puedes borrar un cliente que aún tiene pedidos.

Borrar un cliente con pedidos
DELETE FROM clientes WHERE id = 1;

Resultado esperado (error real de SQLite):

Error: FOREIGN KEY constraint failed

⚠️ Importante · SQLite y PostgreSQL

PostgreSQL siempre hace cumplir las claves foráneas. SQLite las ignora por defecto: debes activarlas en cada conexión con PRAGMA foreign_keys = ON;. Los errores de esta lección salen con esa opción activada.

3.Tipos de relaciones

Según cuántas filas de una tabla se asocian con filas de otra, hay tres tipos:

RelaciónEjemploCómo se guarda
Uno a muchosUn cliente tiene muchos pedidosClave foránea en el lado «muchos»: pedidos.cliente_id
Muchos a muchosUn pedido incluye varios productos y un producto aparece en varios pedidosUna tabla intermedia con dos claves foráneas: lineas_pedido
Uno a unoUn usuario tiene un perfilClave foránea con UNIQUE (poco frecuente)

En lineas_pedido, la clave primaria es la pareja (pedido_id, producto_id): un producto solo puede aparecer una vez en un mismo pedido, con su cantidad. Estos son los datos:

Contenido de lineas_pedido

pedido_idproducto_idcantidad
111
121
251
371
382
462
423

Hasta aquí solo vemos números. Para ver nombres hay que unir las tablas.

4.INNER JOIN: unir filas que coinciden

JOIN combina filas de dos tablas según una condición, escrita tras ON. Con INNER JOIN (o solo JOIN) quedan únicamente las filas que tienen pareja en ambas tablas.

Cada pedido con el nombre de su cliente
SELECT c.nombre, p.id AS pedido, p.fecha
FROM clientes c
INNER JOIN pedidos p ON p.cliente_id = c.id
ORDER BY p.id;

c y p son alias de tabla: abreviaturas para no repetir nombres largos y evitar ambigüedades (c.id frente a p.id). AS pedido renombra la columna en el resultado.

Resultado esperado

nombrepedidofecha
Ana Torres12026-09-01
Ana Torres22026-09-15
Luis Pérez32026-09-20
Marta Ruiz42026-10-02

Diego no aparece: no tiene pedidos, así que no hay pareja para él.

5.LEFT JOIN: conservar todo el lado izquierdo

LEFT JOIN conserva todas las filas de la tabla izquierda (la de FROM) aunque no tengan pareja; en ese caso las columnas de la derecha valen NULL.

Todos los clientes, tengan o no pedidos
SELECT c.nombre, p.id AS pedido, p.fecha
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
ORDER BY c.id, p.id;

Resultado esperado

nombrepedidofecha
Ana Torres12026-09-01
Ana Torres22026-09-15
Luis Pérez32026-09-20
Marta Ruiz42026-10-02
Diego SotoNULLNULL

Esto permite una consulta muy útil: encontrar lo que no tiene relación, filtrando las filas con NULL en la tabla de la derecha (recuerda usar IS NULL, no = NULL).

Clientes que nunca han comprado
SELECT c.nombre
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
WHERE p.id IS NULL;

Resultado esperado

nombre
Diego Soto

📌 Nota · ¿Y RIGHT y FULL JOIN?

RIGHT JOIN es el espejo de LEFT JOIN y FULL JOIN conserva ambos lados. PostgreSQL los admite siempre; SQLite solo desde la versión 3.39. En la práctica casi siempre basta con reordenar las tablas y usar LEFT JOIN.

6.Unir tres o más tablas

Puedes encadenar JOIN. Para saber qué contiene el pedido 4 hay que pasar por la tabla intermedia:

Detalle del pedido 4
SELECT p.id AS pedido, pr.nombre AS producto, l.cantidad, pr.precio
FROM pedidos p
JOIN lineas_pedido l ON l.pedido_id  = p.id
JOIN productos pr    ON pr.id        = l.producto_id
WHERE p.id = 4;

Resultado esperado

pedidoproductocantidadprecio
4Ratón345.50
4Altavoz299.00

Y con cuatro tablas obtienes el historial completo de compras:

Quién compró qué y cuándo
SELECT c.nombre, p.fecha, pr.nombre AS producto, l.cantidad
FROM clientes c
JOIN pedidos p        ON p.cliente_id  = c.id
JOIN lineas_pedido l  ON l.pedido_id   = p.id
JOIN productos pr     ON pr.id         = l.producto_id
ORDER BY p.fecha, pr.nombre;

Resultado esperado

nombrefechaproductocantidad
Ana Torres2026-09-01Ratón1
Ana Torres2026-09-01Teclado1
Ana Torres2026-09-15Webcam1
Luis Pérez2026-09-20Cable HDMI2
Luis Pérez2026-09-20Monitor 4K1
Marta Ruiz2026-10-02Altavoz2
Marta Ruiz2026-10-02Ratón3

Si ves filas de más o repetidas, casi siempre falta o sobra una condición ON.

7.Pruébalo: laboratorio de JOIN

Cambia el tipo de unión y observa qué filas aparecen. Las filas con NULL (clientes sin pedido) se resaltan. Los resultados son los de SQLite real sobre estos datos.

🔗 Laboratorio de JOIN: clientes y pedidos

    

🧪 Ejemplo · Experimentos

1) Con INNER JOIN cuenta las filas y localiza a Diego: no está. 2) Cambia a LEFT JOIN: aparece con NULL. 3) Marca «Solo sin pareja» con INNER JOIN: no queda ninguna fila, porque el inner solo conserva parejas. 4) Haz lo mismo con LEFT JOIN: queda solo Diego.

8.Errores frecuentes

📒 Apuntes para llevar

  • Una clave foránea es una columna que apunta a la clave primaria de otra tabla: cliente_id INTEGER REFERENCES clientes(id).
  • Impide datos huérfanos: no se crea un pedido de un cliente inexistente ni se borra un cliente con pedidos. En SQLite activa PRAGMA foreign_keys = ON.
  • Uno a muchos: la clave foránea va en el lado «muchos». Muchos a muchos: tabla intermedia con dos claves foráneas.
  • INNER JOIN … ON … devuelve solo las filas con pareja en ambas tablas.
  • LEFT JOIN conserva todas las filas de la tabla izquierda; sin pareja, las columnas de la derecha son NULL.
  • LEFT JOIN … WHERE derecha.id IS NULL encuentra filas sin relación (clientes sin pedidos).
  • Usa alias cortos (c, p) y califica las columnas (c.nombre) cuando unes varias tablas.

9.Glosario

TérminoSignificado
Clave foráneaColumna que guarda la clave primaria de una fila de otra tabla para relacionarlas.
Integridad referencialGarantía de que toda clave foránea apunta a una fila que existe.
JOINOperación que combina filas de dos tablas según una condición.
INNER JOINUnión que conserva solo las filas con pareja en ambas tablas.
LEFT JOINUnión que conserva todas las filas de la tabla izquierda; completa con NULL si no hay pareja.
AliasNombre corto temporal para una tabla o columna (clientes c, AS pedido).
Tabla intermediaTabla que implementa una relación muchos a muchos con dos claves foráneas.
Producto cartesianoTodas las combinaciones posibles entre filas de dos tablas; aparece al omitir la condición del JOIN.

🧠 Comprueba lo aprendido

Elige una respuesta en cada pregunta. Verás al instante si acertaste y por qué.

¿Qué hace cliente_id INTEGER REFERENCES clientes(id)?

¿Cómo se representa una relación muchos a muchos entre pedidos y productos?

Con 4 clientes, de los cuales uno no tiene pedidos, ¿qué diferencia hay entre INNER JOIN y LEFT JOIN de clientes a pedidos?

¿Cómo encuentras los clientes que nunca han hecho un pedido?

¿Qué ocurre si olvidas la condición ON en un JOIN?

Tu progreso se guarda en este navegador y se refleja en el índice del curso.

💡 🚀 ¡Ahora te toca!

La práctica fija lo aprendido. Pasa al ejercicio de esta lección.

Ir al ejercicio