Los JOINs son la herramienta fundamental para combinar datos de varias tablas en SQL. Son el corazón de las bases de datos relacionales: la razón por la que los datos se organizan en tablas separadas en lugar de una sola tabla gigante es precisamente para poder combinarlos de formas flexibles. Sin embargo, la diferencia entre los distintos tipos de JOIN confunde a muchos desarrolladores que empiezan con SQL.
En esta guía vas a entender cada tipo de JOIN con ejemplos reales y visualizaciones de los datos que devuelve cada uno.
El esquema de ejemplo
Vamos a usar dos tablas pequeñas para que los resultados sean fáciles de seguir. Un conjunto de clientes y un conjunto de pedidos, con la particularidad de que no todos los clientes tienen pedidos y no todos los pedidos tienen un cliente registrado (para ilustrar bien las diferencias).
CREATE TABLE clientes (
id INT PRIMARY KEY,
nombre VARCHAR(100)
);
CREATE TABLE pedidos (
id INT PRIMARY KEY,
cliente_id INT,
total DECIMAL(10, 2),
producto VARCHAR(100)
);
INSERT INTO clientes VALUES
(1, 'Ana García'),
(2, 'Carlos López'),
(3, 'María Torres'),
(4, 'Jorge Ramírez'); -- no tiene pedidos
INSERT INTO pedidos VALUES
(101, 1, 59.99, 'Teclado'),
(102, 1, 129.00, 'Monitor'),
(103, 2, 19.99, 'Ratón'),
(104, NULL, 89.00, 'Auriculares'), -- sin cliente registrado
(105, 5, 45.00, 'Webcam'); -- cliente_id 5 no existe en clientes
Estado de las tablas:
-- clientes:
-- id nombre
-- 1 Ana García
-- 2 Carlos López
-- 3 María Torres
-- 4 Jorge Ramírez ← no tiene ningún pedido
-- pedidos:
-- id cliente_id total producto
-- 101 1 59.99 Teclado
-- 102 1 129.00 Monitor
-- 103 2 19.99 Ratón
-- 104 NULL 89.00 Auriculares ← sin cliente
-- 105 5 45.00 Webcam ← cliente_id 5 no existe
INNER JOIN: solo las filas que coinciden en ambas tablas
El INNER JOIN devuelve únicamente las filas que tienen correspondencia en ambas tablas. Si una fila de la tabla izquierda no tiene coincidencia en la tabla derecha (o viceversa), esa fila queda fuera del resultado.
SELECT
c.id AS cliente_id,
c.nombre AS cliente,
p.id AS pedido_id,
p.total,
p.producto
FROM clientes c
INNER JOIN pedidos p ON p.cliente_id = c.id;
-- Resultado:
-- cliente_id cliente pedido_id total producto
-- 1 Ana García 101 59.99 Teclado
-- 1 Ana García 102 129.00 Monitor
-- 2 Carlos López 103 19.99 Ratón
-- ¿Qué quedó fuera?
-- Jorge Ramírez (id=4): no tiene ningún pedido → excluido
-- María Torres (id=3): no tiene ningún pedido → excluida
-- Pedido 104 (cliente_id=NULL): NULL no coincide con ningún id → excluido
-- Pedido 105 (cliente_id=5): no existe cliente con id=5 → excluido
La palabra INNER es opcional: JOIN a secas es un INNER JOIN. Son equivalentes.
-- Estas dos consultas son idénticas
SELECT * FROM clientes c JOIN pedidos p ON p.cliente_id = c.id;
SELECT * FROM clientes c INNER JOIN pedidos p ON p.cliente_id = c.id;
Úsalo cuando quieras solo los registros que tienen relación en las dos tablas. Es el más común en el día a día.
LEFT JOIN: todas las filas de la izquierda, las coincidentes de la derecha
El LEFT JOIN (o LEFT OUTER JOIN) devuelve todas las filas de la tabla izquierda (la que aparece antes del JOIN), más las filas coincidentes de la tabla derecha. Si una fila de la tabla izquierda no tiene correspondencia en la derecha, aparece en el resultado con NULL en las columnas de la tabla derecha.
SELECT
c.id AS cliente_id,
c.nombre AS cliente,
p.id AS pedido_id,
p.total,
p.producto
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id;
-- Resultado:
-- cliente_id cliente pedido_id total producto
-- 1 Ana García 101 59.99 Teclado
-- 1 Ana García 102 129.00 Monitor
-- 2 Carlos López 103 19.99 Ratón
-- 3 María Torres NULL NULL NULL ← sin pedidos, pero aparece
-- 4 Jorge Ramírez NULL NULL NULL ← sin pedidos, pero aparece
-- ¿Qué quedó fuera?
-- Pedido 104 (cliente_id=NULL): no coincide con ningún cliente → excluido
-- Pedido 105 (cliente_id=5): no existe cliente con id=5 → excluido
Caso de uso clásico del LEFT JOIN: encontrar filas sin correspondencia
Una de las aplicaciones más útiles del LEFT JOIN es encontrar exactamente los registros que no tienen correspondencia: clientes sin pedidos, productos sin ventas, empleados sin manager asignado.
-- Clientes que NO han hecho ningún pedido
SELECT c.id, c.nombre
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
WHERE p.id IS NULL; -- ← la clave: filtrar donde el JOIN no encontró nada
-- Resultado:
-- id nombre
-- 3 María Torres
-- 4 Jorge Ramírez
Este patrón (LEFT JOIN + WHERE tabla_derecha.columna IS NULL) es equivalente a NOT EXISTS o NOT IN, pero suele ser más eficiente y más claro de leer.
RIGHT JOIN: todas las filas de la derecha, las coincidentes de la izquierda
El RIGHT JOIN (o RIGHT OUTER JOIN) es el espejo del LEFT JOIN: devuelve todas las filas de la tabla derecha (la que aparece después del JOIN), más las filas coincidentes de la tabla izquierda. Las filas de la tabla derecha sin correspondencia aparecen con NULL en las columnas de la tabla izquierda.
SELECT
c.id AS cliente_id,
c.nombre AS cliente,
p.id AS pedido_id,
p.total,
p.producto
FROM clientes c
RIGHT JOIN pedidos p ON p.cliente_id = c.id;
-- Resultado:
-- cliente_id cliente pedido_id total producto
-- 1 Ana García 101 59.99 Teclado
-- 1 Ana García 102 129.00 Monitor
-- 2 Carlos López 103 19.99 Ratón
-- NULL NULL 104 89.00 Auriculares ← sin cliente, pero aparece
-- NULL NULL 105 45.00 Webcam ← sin cliente válido, aparece
-- ¿Qué quedó fuera?
-- María Torres (id=3): no tiene pedidos → excluida (estaba en la tabla izquierda)
-- Jorge Ramírez (id=4): no tiene pedidos → excluido (estaba en la tabla izquierda)
RIGHT JOIN es poco habitual: por qué
En la práctica, el RIGHT JOIN se usa muy poco porque puedes conseguir exactamente el mismo resultado invirtiendo el orden de las tablas y usando LEFT JOIN. La mayoría de desarrolladores prefieren escribir siempre LEFT JOIN y cambiar el orden de las tablas en lugar de mezclar LEFT y RIGHT en la misma consulta.
-- Estas dos consultas devuelven el mismo resultado:
SELECT c.nombre, p.producto
FROM clientes c
RIGHT JOIN pedidos p ON p.cliente_id = c.id;
-- Equivalente más legible con LEFT JOIN:
SELECT c.nombre, p.producto
FROM pedidos p
LEFT JOIN clientes c ON c.id = p.cliente_id;
FULL JOIN: todas las filas de ambas tablas
El FULL JOIN (o FULL OUTER JOIN) devuelve todas las filas de ambas tablas, haya coincidencia o no. Donde no hay coincidencia, las columnas de la tabla que no aportó datos aparecen como NULL. Es la unión completa de LEFT JOIN y RIGHT JOIN.
-- PostgreSQL: FULL JOIN es soporte nativo
SELECT
c.id AS cliente_id,
c.nombre AS cliente,
p.id AS pedido_id,
p.total,
p.producto
FROM clientes c
FULL JOIN pedidos p ON p.cliente_id = c.id;
-- Resultado:
-- cliente_id cliente pedido_id total producto
-- 1 Ana García 101 59.99 Teclado
-- 1 Ana García 102 129.00 Monitor
-- 2 Carlos López 103 19.99 Ratón
-- 3 María Torres NULL NULL NULL ← cliente sin pedidos
-- 4 Jorge Ramírez NULL NULL NULL ← cliente sin pedidos
-- NULL NULL 104 89.00 Auriculares ← pedido sin cliente
-- NULL NULL 105 45.00 Webcam ← pedido sin cliente
FULL JOIN en MySQL: no existe nativamente
MySQL no soporta FULL JOIN. La forma de simularlo es combinar un LEFT JOIN y un RIGHT JOIN con UNION:
-- MySQL: simular FULL JOIN con UNION de LEFT y RIGHT JOIN
SELECT c.id AS cliente_id, c.nombre, p.id AS pedido_id, p.total, p.producto
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
UNION
SELECT c.id AS cliente_id, c.nombre, p.id AS pedido_id, p.total, p.producto
FROM clientes c
RIGHT JOIN pedidos p ON p.cliente_id = c.id;
-- UNION elimina duplicados automáticamente
-- Si quieres conservar duplicados, usa UNION ALL (más eficiente pero puede repetir filas)
CROSS JOIN: el producto cartesiano
El CROSS JOIN combina cada fila de la tabla izquierda con cada fila de la tabla derecha. Si la tabla A tiene 4 filas y la tabla B tiene 5 filas, el resultado tendrá 4 × 5 = 20 filas. No lleva condición ON.
SELECT c.nombre, p.producto
FROM clientes c
CROSS JOIN pedidos p;
-- Resultado: 4 clientes × 5 pedidos = 20 filas
-- (cada cliente combinado con cada pedido, sin tener en cuenta relaciones)
-- Caso de uso real: generar todas las combinaciones posibles
CREATE TABLE tallas (talla VARCHAR(5));
CREATE TABLE colores (color VARCHAR(20));
CREATE TABLE materiales (material VARCHAR(20));
INSERT INTO tallas VALUES ('S'), ('M'), ('L'), ('XL');
INSERT INTO colores VALUES ('Negro'), ('Blanco'), ('Rojo');
INSERT INTO materiales VALUES ('Algodón'), ('Poliéster');
-- Generar el catálogo completo: 4 × 3 × 2 = 24 combinaciones
SELECT t.talla, c.color, m.material
FROM tallas t
CROSS JOIN colores c
CROSS JOIN materiales m
ORDER BY t.talla, c.color, m.material;
SELF JOIN: unir una tabla consigo misma
Un SELF JOIN es cuando una tabla se une consigo misma. Se usa para relaciones jerárquicas o cuando una tabla tiene una columna que referencia otra fila de la misma tabla.
CREATE TABLE empleados (
id INT PRIMARY KEY,
nombre VARCHAR(100),
manager_id INT -- referencia al id de otro empleado de la misma tabla
);
INSERT INTO empleados VALUES
(1, 'Ana (CEO)', NULL),
(2, 'Carlos (Ingeniería)', 1),
(3, 'María (Marketing)', 1),
(4, 'Jorge (Dev)', 2),
(5, 'Lucía (Dev)', 2);
-- Mostrar cada empleado con el nombre de su manager
SELECT
e.nombre AS empleado,
m.nombre AS manager
FROM empleados e
LEFT JOIN empleados m ON m.id = e.manager_id; -- la misma tabla con alias distintos
-- Resultado:
-- empleado manager
-- Ana (CEO) NULL
-- Carlos (Ingeniería) Ana (CEO)
-- María (Marketing) Ana (CEO)
-- Jorge (Dev) Carlos (Ingeniería)
-- Lucía (Dev) Carlos (Ingeniería)
Comparativa visual de todos los JOINs
| Tipo de JOIN | Filas de la izquierda sin coincidencia | Filas coincidentes | Filas de la derecha sin coincidencia |
|---|---|---|---|
| INNER JOIN | No | Sí | No |
| LEFT JOIN | Sí (con NULL en cols de la derecha) | Sí | No |
| RIGHT JOIN | No | Sí | Sí (con NULL en cols de la izquierda) |
| FULL JOIN | Sí (con NULL) | Sí | Sí (con NULL) |
| CROSS JOIN | Todas las combinaciones posibles, sin condición | ||
Múltiples JOINs en una sola consulta
-- Una consulta real con varios JOINs encadenados
CREATE TABLE categorias (
id INT PRIMARY KEY,
nombre VARCHAR(50)
);
CREATE TABLE productos (
id INT PRIMARY KEY,
nombre VARCHAR(100),
categoria_id INT REFERENCES categorias(id)
);
-- Consulta que une clientes, pedidos, productos y categorías
SELECT
c.nombre AS cliente,
p.id AS pedido_id,
pr.nombre AS producto,
cat.nombre AS categoria,
p.total
FROM pedidos p
INNER JOIN clientes c ON c.id = p.cliente_id
INNER JOIN productos pr ON pr.id = p.producto_id
LEFT JOIN categorias cat ON cat.id = pr.categoria_id -- LEFT porque puede no tener categoría
WHERE p.total > 50
ORDER BY p.total DESC;
El orden en que escribes los JOINs no afecta al resultado (el optimizador reordena según lo que sea más eficiente), pero sí afecta a la legibilidad. La convención habitual es ir de la tabla central hacia afuera: empieza por la tabla que más filtra o que es el centro lógico de la consulta y añade los JOINs hacia las tablas relacionadas.
Errores comunes con los JOINs
Olvidar la condición ON y generar un producto cartesiano accidental
-- ❌ Sin ON: CROSS JOIN accidental (puede devolver millones de filas)
SELECT * FROM clientes, pedidos; -- sintaxis antigua, une todo con todo
-- ❌ También sin ON en sintaxis moderna (error o producto cartesiano según el motor)
SELECT * FROM clientes JOIN pedidos;
-- ✅ Siempre especifica la condición de unión
SELECT * FROM clientes c JOIN pedidos p ON p.cliente_id = c.id;
Confundir el lado del LEFT JOIN
-- Si quieres TODOS los pedidos aunque no tengan cliente,
-- el pedido debe estar en la tabla izquierda (antes del JOIN)
-- ❌ Esto devuelve todos los clientes (izquierda), no todos los pedidos
SELECT * FROM clientes c LEFT JOIN pedidos p ON p.cliente_id = c.id;
-- ✅ Esto devuelve todos los pedidos (izquierda), con o sin cliente
SELECT * FROM pedidos p LEFT JOIN clientes c ON c.id = p.cliente_id;
Multiplicar filas inesperadamente
-- Si una fila de la tabla izquierda coincide con VARIAS de la derecha,
-- aparecerá repetida en el resultado
-- Ana García tiene 2 pedidos → aparece 2 veces con INNER JOIN
SELECT c.nombre, p.producto
FROM clientes c
INNER JOIN pedidos p ON p.cliente_id = c.id;
-- cliente producto
-- Ana García Teclado
-- Ana García Monitor ← repetida, es correcto si quieres el detalle de pedidos
-- Si solo quieres UNA fila por cliente (el total), usa GROUP BY
SELECT c.nombre, COUNT(p.id) AS num_pedidos, SUM(p.total) AS total_gastado
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
GROUP BY c.id, c.nombre;
Resumen: cuándo usar cada JOIN
- Usa INNER JOIN cuando quieras solo los registros que tienen relación en ambas tablas. Es el más común: pedidos con sus clientes, productos con sus categorías.
- Usa LEFT JOIN cuando quieras todos los registros de la tabla izquierda, haya o no coincidencia en la derecha. Muy útil para encontrar registros huérfanos (WHERE tabla_derecha.id IS NULL) o para informes que deben incluir todos los registros aunque algunos no tengan datos relacionados.
- Usa RIGHT JOIN raramente: en casi todos los casos puedes invertir el orden de las tablas y usar LEFT JOIN, que es más fácil de leer y razonar.
- Usa FULL JOIN cuando necesites todos los registros de ambas tablas sin importar si tienen correspondencia o no. Útil para auditorías y reconciliaciones de datos. En MySQL, simúlalo con UNION de LEFT y RIGHT JOIN.
- Usa CROSS JOIN cuando necesites todas las combinaciones posibles entre dos conjuntos. Poco frecuente, pero indispensable para generar catálogos o matrices.
- Usa SELF JOIN para relaciones jerárquicas dentro de la misma tabla: empleado-manager, categoría-subcategoría.
No hay comentarios todavía. Sé el primero en compartir tu opinión.