🧚 ¡Oye! Beekeeper Studio es una GUI de bases de datos rápida, moderna y de código abierto Descarga
2 de julio de 2025 Por Matthew Rathbone

Los JOINs de SQL combinan filas de dos tablas haciendo coincidir una columna que comparten, como el author_id de un libro y el id de un autor. El tipo de join decide qué pasa con las filas que no coinciden. Los ejemplos funcionan en PostgreSQL, MySQL, SQLite y SQL Server.

Cómo usar los JOINs de SQL

Pon la segunda tabla después de JOIN e indica en ON cómo coinciden sus filas con las de la primera tabla:

SELECT columns
FROM table_a
JOIN table_b ON table_a.key = table_b.key;

Todos los ejemplos de esta sección unen las mismas dos tablas: Butler escribió dos de los libros, Asimov no escribió ninguno y nadie sabe quién escribió Beowulf.

INNER JOIN

Diagrama de INNER JOIN

Un INNER JOIN devuelve solo las filas que coinciden en ambas tablas. JOIN por sí solo significa INNER JOIN.

-- Input: authors
-- id | name
-- ---+--------
-- 1  | Le Guin
-- 2  | Butler
-- 3  | Asimov

-- Input: books
-- title                | author_id
-- ---------------------+----------
-- The Dispossessed     | 1
-- Kindred              | 2
-- Parable of the Sower | 2
-- Beowulf              | NULL

SELECT a.name, b.title
FROM authors a
INNER JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower

Asimov no tiene libros y Beowulf no tiene autor, así que ninguno aparece.

LEFT JOIN

Diagrama de LEFT JOIN

Un LEFT JOIN (o LEFT OUTER JOIN) devuelve todas las filas de la tabla izquierda, la que va después de FROM. Cuando la tabla derecha no tiene coincidencia, sus columnas vuelven como NULL.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- Asimov  | NULL

RIGHT JOIN

Diagrama de RIGHT JOIN

Un RIGHT JOIN (o RIGHT OUTER JOIN) es su reflejo: todas las filas de la tabla derecha, con NULLs donde la tabla izquierda no tiene coincidencia.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- NULL    | Beowulf

FULL OUTER JOIN

Diagrama de FULL OUTER JOIN

Un FULL OUTER JOIN devuelve todas las filas de ambas tablas, emparejadas cuando es posible y rellenadas con NULLs cuando no. MySQL no lo tiene: consulta “Sintaxis alternativa” más abajo.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
FULL OUTER JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- NULL    | Beowulf
-- Asimov  | NULL

CROSS JOIN

Diagrama de CROSS JOIN

Un CROSS JOIN empareja cada fila de una tabla con cada fila de la otra, por eso no lleva ON. Dos tallas y dos colores dan cuatro combinaciones; 1,000 filas por 1,000 filas dan un millón, así que úsalo con tablas pequeñas.

-- Input: sizes
-- size
-- ----
-- S
-- M

-- Input: colors
-- color
-- -----
-- Red
-- Blue

SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;

-- Output:
-- size | color
-- -----+------
-- S    | Red
-- S    | Blue
-- M    | Red
-- M    | Blue

LEFT ANTI JOIN

Diagrama de LEFT ANTI JOIN

Un left anti join devuelve las filas de la tabla izquierda que no tienen coincidencia en la tabla derecha. No existe una palabra clave para esto: haz un LEFT JOIN y quédate con las filas donde el lado derecho vino como NULL.

-- Input: the authors and books tables above

SELECT a.name
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.author_id IS NULL;

-- Output:
-- name
-- ------
-- Asimov

RIGHT ANTI JOIN

Diagrama de RIGHT ANTI JOIN

Un right anti join devuelve las filas de la tabla derecha que no tienen coincidencia en la tabla izquierda.

-- Input: the authors and books tables above

SELECT b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;

-- Output:
-- title
-- -------
-- Beowulf

Reglas que debes conocer

  • NULL nunca coincide con nada, ni siquiera con otro NULL. El author_id NULL de Beowulf no coincide con ningún autor, así que solo lo devuelven los joins que conservan los libros sin coincidencia.
  • Una fila aparece una vez por cada fila con la que coincide. Butler aparece dos veces arriba porque coincide con dos libros. Si luego sumas una columna de la tabla de autores, sus valores cuentan doble.
  • Con un outer join, una condición sobre la otra tabla va en ON. En WHERE, se ejecuta después del join y descarta las filas NULL que el join acababa de conservar:
-- Input: the authors and books tables above

-- Only Butler is left: the WHERE drops every author whose title is NULL
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.title LIKE 'K%';

-- Output:
-- name   | title
-- -------+--------
-- Butler | Kindred

-- Every author is kept, with a book only where it starts with K
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id AND b.title LIKE 'K%';

-- Output:
-- name    | title
-- --------+--------
-- Le Guin | NULL
-- Butler  | Kindred
-- Asimov  | NULL

Sintaxis alternativa

MySQL no tiene FULL OUTER JOIN. En su lugar, combina un LEFT JOIN con las filas sin coincidencia de un RIGHT JOIN:

-- MySQL
-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a LEFT JOIN books b ON a.id = b.author_id
UNION ALL
SELECT a.name, b.title
FROM authors a RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Parable of the Sower
-- Butler  | Kindred
-- Asimov  | NULL
-- NULL    | Beowulf

SQLite antes de la versión 3.39 no tiene RIGHT JOIN ni FULL OUTER JOIN: invierte las tablas y usa un LEFT JOIN.

Un anti join también se puede escribir con NOT EXISTS. Evita NOT IN para esto: si la subconsulta devuelve un solo NULL, como el author_id de Beowulf aquí, NOT IN no devuelve absolutamente nada:

-- Input: the authors and books tables above

SELECT a.name
FROM authors a
WHERE NOT EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id);

-- Output:
-- name
-- ------
-- Asimov

SELECT a.name
FROM authors a
WHERE a.id NOT IN (SELECT author_id FROM books);

-- Output:
-- name
-- ----
-- (0 rows)

Cuando las columnas a comparar tienen el mismo nombre en ambas tablas, puedes escribir JOIN books USING (author_id) en lugar de la condición ON, excepto en SQL Server.

El código más antiguo suele listar las tablas separadas por comas y poner la coincidencia en WHERE. Es un INNER JOIN escrito de otra forma. Si omites el WHERE, se convierte en un CROSS JOIN:

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a, books b
WHERE a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower

¿Cuándo deberías usar JOINs de SQL?

Usa un join siempre que los datos que necesitas estén repartidos entre varias tablas. Cuál usar depende de qué debe pasar con las filas que no coinciden, y la mayoría de los problemas caen en uno de estos cinco casos.

Una fila de empleado guarda un ID de departamento, pero el reporte necesita el nombre del departamento. Un INNER JOIN lo obtiene y descarta cualquier fila que no tenga nada que buscar.

Conservar filas sin coincidencia

Cuando los datos relacionados son opcionales, como un empleado al que aún no se le asigna departamento, un INNER JOIN lo ocultaría sin avisar. Un LEFT JOIN conserva todas las filas de la tabla principal y llena los huecos con NULL.

Listar todos los elementos, incluso los vacíos

Los reportes suelen necesitar todos los departamentos, categorías o clientes, incluidos los que todavía no tienen nada. Haz que esa sea la tabla cuyas filas se conservan todas: primera con un LEFT JOIN, o segunda con un RIGHT JOIN.

Encontrar filas sin coincidencia

Clientes que nunca compraron, productos que nunca se vendieron, empleados sin departamento: un anti join encuentra lo que falta, lo que también lo hace una buena verificación de calidad de datos.

Generar todas las combinaciones

Un CROSS JOIN genera filas que todavía no existen: cada talla en cada color, cada empleado con cada curso de capacitación o cada tienda en cada día de un reporte.

Ejemplos reales de JOINs de SQL

Estudiantes y sus cursos

Una relación muchos a muchos necesita dos INNER JOINs, a través de la tabla intermedia que vincula estudiantes con cursos. Mei no está inscrita en nada, así que no aparece:

-- Input: students
-- id | name
-- ---+------
-- 1  | Amara
-- 2  | Lucas
-- 3  | Mei

-- Input: enrollments
-- student_id | course_id | grade
-- -----------+-----------+------
-- 1          | 10        | A
-- 1          | 20        | B
-- 2          | 10        | B

-- Input: courses
-- id | course_name
-- ---+------------
-- 10 | Databases
-- 20 | Statistics

SELECT s.name, c.course_name, e.grade
FROM students s
INNER JOIN enrollments e ON s.id = e.student_id
INNER JOIN courses c ON e.course_id = c.id;

-- Output:
-- name  | course_name | grade
-- ------+-------------+------
-- Amara | Databases   | A
-- Amara | Statistics  | B
-- Lucas | Databases   | B

Usuarios con perfiles opcionales

No todos los usuarios han llenado su perfil. Un LEFT JOIN los conserva a todos:

-- Input: users
-- id | username
-- ---+---------
-- 1  | rosa_m
-- 2  | devon
-- 3  | kai_l

-- Input: user_profiles
-- user_id | bio
-- --------+------------------------
-- 1       | Data engineer in Lisbon
-- 3       | Loves SQL puzzles

SELECT u.username, p.bio
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;

-- Output:
-- username | bio
-- ---------+------------------------
-- rosa_m   | Data engineer in Lisbon
-- devon    | NULL
-- kai_l    | Loves SQL puzzles

Número de pedidos por cliente

Para incluir a los clientes sin pedidos, haz un LEFT JOIN desde customers. Luego cuenta una columna de la tabla de pedidos: COUNT(*) cuenta la fila que el LEFT JOIN inventó para Omar, así que tendría 1 en vez de 0:

-- Input: customers
-- id | name
-- ---+------------
-- 1  | Harper Lee
-- 2  | Omar Haddad
-- 3  | Yuki Tanaka

-- Input: orders
-- id  | customer_id | total
-- ----+-------------+-------
-- 101 | 1           | 59.90
-- 102 | 3           | 120.00
-- 103 | 1           | 14.50

-- Wrong: COUNT(*) counts Omar's NULL row
SELECT c.name, COUNT(*) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;

-- Output:
-- name        | order_count
-- ------------+------------
-- Harper Lee  | 2
-- Omar Haddad | 1
-- Yuki Tanaka | 1

-- Right: COUNT(o.id) skips it
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;

-- Output:
-- name        | order_count
-- ------------+------------
-- Harper Lee  | 2
-- Omar Haddad | 0
-- Yuki Tanaka | 1

Facturas y pagos que no cuadran

Un FULL OUTER JOIN que conserva solo las filas sin coincidencia compara dos listas entre sí en una sola consulta: la factura 502 no se ha pagado y el pago 9003 corresponde a una factura que no existe:

-- Input: invoices
-- invoice_id | amount
-- -----------+-------
-- 501        | 120.00
-- 502        | 75.50
-- 503        | 300.00

-- Input: payments
-- payment_id | invoice_id | paid
-- -----------+------------+-------
-- 9001       | 501        | 120.00
-- 9002       | 503        | 300.00
-- 9003       | 504        | 42.00

-- MySQL has no FULL OUTER JOIN: use the UNION ALL form under "Alternative syntax"
SELECT i.invoice_id, i.amount, p.payment_id, p.invoice_id AS paid_invoice
FROM invoices i
FULL OUTER JOIN payments p ON p.invoice_id = i.invoice_id
WHERE i.invoice_id IS NULL OR p.payment_id IS NULL;

-- Output:
-- invoice_id | amount | payment_id | paid_invoice
-- -----------+--------+------------+-------------
-- 502        | 75.50  | NULL       | NULL
-- NULL       | NULL   | 9003       | 504

Ventas diarias por tienda, incluidos los días sin ventas

Haz un CROSS JOIN de las tiendas con los días para obtener una fila por tienda por día, y luego un LEFT JOIN de las ventas sobre esa cuadrícula. York no vendió nada el 6 de octubre y aun así tiene su fila:

-- Input: stores
-- store
-- -----
-- Leeds
-- York

-- Input: days
-- day
-- ----------
-- 2026-10-05
-- 2026-10-06

-- Input: sales
-- store | day        | amount
-- ------+------------+-------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York  | 2026-10-05 | 220.00

SELECT st.store, d.day, COALESCE(SUM(s.amount), 0.00) AS takings
FROM stores st
CROSS JOIN days d
LEFT JOIN sales s ON s.store = st.store AND s.day = d.day
GROUP BY st.store, d.day
ORDER BY st.store, d.day;

-- Output:
-- store | day        | takings
-- ------+------------+--------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York  | 2026-10-05 | 220.00
-- York  | 2026-10-06 | 0.00

Más lecturas sobre JOINs de SQL

En este blog:

Documentación oficial:

Beekeeper Studio es un cliente de bases de datos gratuito y de código abierto

La mejor herramienta de consultas y edición de SQL que he usado. Tiene todo lo que necesito para administrar mi base de datos. - ⭐⭐⭐⭐⭐ Mit

Beekeeper Studio es rápido, intuitivo y fácil de usar. Es compatible con muchísimas bases de datos y funciona de maravilla en Windows, Mac y Linux.

La versión de Beekeeper para Linux tiene el 100% de las funciones, sin recortes ni concesiones.

Lo que dicen los usuarios de Beekeeper Studio

★★★★★
"Beekeeper Studio reemplazó por completo mi antiguo flujo de trabajo con SQL. Es rápido, intuitivo y hace que trabajar con bases de datos vuelva a ser agradable."
— Alex K., desarrollador de bases de datos
★★★★★
"He probado muchas GUIs de bases de datos, pero Beekeeper logra el equilibrio perfecto entre funciones y simplicidad. Simplemente funciona."
— Sarah M., ingeniera full stack

¿Listo para mejorar tu flujo de trabajo con SQL?

download Descarga gratis