JOINs en MySQL
Las bases de datos relacionales dividen la información en tablas separadas para evitar repetición. Los JOINs combinan esas tablas en una sola consulta, conectando filas que comparten datos en común.
¿Por qué existe el problema que resuelve JOIN?
Mirá cómo está diseñada nuestra base de datos. La tabla ordenes tiene
una columna id_cliente, pero no tiene el nombre del cliente.
¿Por qué? Porque si guardáramos el nombre en cada orden, estaríamos repitiendo
datos — y si el cliente cambia de nombre, habría que actualizar miles de filas.
En cambio, el nombre vive en la tabla clientes una sola vez,
y la tabla ordenes guarda solo el número que lo identifica:
-- Tabla clientes -- Tabla ordenes
-- id | nombre -- id | id_cliente | total
-- 1 | Ana García -- 1 | 3 | 450.00
-- 2 | Carlos López -- 2 | 1 | 1200.00
-- 3 | Beatriz Torres -- 3 | 1 | 89.00
Si consultás solo la tabla ordenes, ves id_cliente = 1 pero no sabés quién es.
El JOIN "junta" las dos tablas usando esa relación:
SELECT o.id, c.nombre, o.total
FROM ordenes o
JOIN clientes c ON o.id_cliente = c.id;
-- Resultado:
-- id | nombre | total
-- 1 | Beatriz Torres | 450.00
-- 2 | Ana García | 1200.00
-- 3 | Ana García | 89.00
ON o.id_cliente = c.id es la condición de unión: le dice a MySQL qué columnas usar para conectar las filas. Cada fila de ordenes se combina con la fila de clientes cuyo id coincide. Si olvidás el ON, MySQL combina todas las filas con todas (producto cartesiano) — resultado: millones de filas incorrectas.
INNER JOIN — solo los que tienen coincidencia
El INNER JOIN (o simplemente JOIN) devuelve únicamente las filas
que tienen coincidencia en ambas tablas.
Si una orden tiene un id_cliente que no existe en clientes, esa orden no aparece.
Si un cliente no tiene ninguna orden, tampoco aparece.
-- Solo aparecen las órdenes que tienen un cliente válido
SELECT
o.id AS nro_orden,
c.nombre AS cliente,
c.ciudad,
o.fecha,
o.estado,
o.total
FROM ordenes o
INNER JOIN clientes c ON o.id_cliente = c.id
ORDER BY o.fecha DESC;
En MySQL podés escribir solo JOIN y es lo mismo que INNER JOIN. Muchos prefieren escribirlo explícito para dejar claro el tipo de unión que están haciendo.
LEFT JOIN — todos los de la tabla izquierda
El LEFT JOIN devuelve todas las filas de la tabla que está en el FROM
(la "izquierda"), tengan o no coincidencia en la otra tabla.
Para las filas sin coincidencia, las columnas de la tabla derecha aparecen como NULL.
Ejemplo concreto: si un cliente nunca hizo una orden, con INNER JOIN no aparece.
Con LEFT JOIN sí aparece, con total_ordenes = 0:
-- INNER JOIN: solo clientes CON órdenes
SELECT c.nombre, COUNT(o.id) AS total_ordenes
FROM clientes c
INNER JOIN ordenes o ON c.id = o.id_cliente
GROUP BY c.id, c.nombre;
-- LEFT JOIN: TODOS los clientes, hayan comprado o no
SELECT c.nombre, COUNT(o.id) AS total_ordenes
FROM clientes c
LEFT JOIN ordenes o ON c.id = o.id_cliente
GROUP BY c.id, c.nombre
ORDER BY total_ordenes DESC;
Cuando las columnas de la tabla derecha son NULL, significa que no hubo coincidencia.
Podés usar eso para encontrar registros "solos":
-- Clientes que NUNCA compraron nada
-- LEFT JOIN trae a todos; WHERE filtra solo los que tienen NULL en ordenes
SELECT c.nombre, c.email
FROM clientes c
LEFT JOIN ordenes o ON c.id = o.id_cliente
WHERE o.id IS NULL;
COALESCE(valor, alternativa) devuelve el primer valor que no sea NULL. Cuando usás SUM(o.total) con LEFT JOIN, los clientes sin órdenes dan NULL. Con COALESCE(SUM(o.total), 0) mostrás 0 en vez de nada.
Comparación visual de los JOINs
| Tipo | ¿Qué incluye? | Cuándo usarlo |
|---|---|---|
INNER JOIN |
Solo filas con coincidencia en ambas tablas | El caso más común. Cuando necesitás datos de ambas tablas. |
LEFT JOIN |
Todas las filas de la tabla izquierda. Las de la derecha pueden ser NULL. | Cuando necesitás todos los registros de una tabla, incluso los que no tienen relación. |
RIGHT JOIN |
Todas las filas de la tabla derecha. Las de la izquierda pueden ser NULL. | Poco usado — casi siempre se reescribe como LEFT JOIN invirtiendo las tablas. |
Unir más de dos tablas
Podés encadenar tantos JOINs como necesites. Cada uno agrega una tabla más al resultado.
Lo importante es que cada JOIN tenga su propio ON indicando cómo se conecta
con lo que ya tenés.
En nuestra base de datos, para saber qué productos compró cada cliente hay que pasar por tres tablas:
clientes → ordenes → detalle_ordenes → productos.
SELECT
c.nombre AS cliente,
o.id AS orden,
p.nombre AS producto,
d.cantidad,
d.precio_unitario,
d.cantidad * d.precio_unitario AS subtotal
FROM detalle_ordenes d
JOIN ordenes o ON d.id_orden = o.id -- detalle → orden
JOIN clientes c ON o.id_cliente = c.id -- orden → cliente
JOIN productos p ON d.id_producto = p.id -- detalle → producto
ORDER BY c.nombre, o.id;
Con varias tablas, escribir el nombre completo todo el tiempo es tedioso y propenso a errores. Asignale una letra corta a cada tabla (FROM ordenes o) y usá el alias en el resto de la query (o.id, o.total). Hace el código mucho más fácil de leer.
Combinar JOIN con GROUP BY
Los JOINs y el GROUP BY se usan juntos constantemente en reportes reales. Primero unís las tablas para tener toda la información junta, luego agrupás para resumirla.
-- ¿Cuánto gastó cada cliente en total?
SELECT
c.nombre AS cliente,
c.ciudad,
COUNT(DISTINCT o.id) AS total_ordenes,
SUM(d.cantidad) AS productos_comprados,
ROUND(SUM(d.cantidad * d.precio_unitario), 2) AS gasto_total
FROM clientes c
JOIN ordenes o ON c.id = o.id_cliente
JOIN detalle_ordenes d ON o.id = d.id_orden
GROUP BY c.id, c.nombre, c.ciudad
ORDER BY gasto_total DESC
LIMIT 5;
Probá el Ejemplo 4: Top clientes por gasto
Buenas Prácticas
- Siempre poné la condición ON — olvidarla genera un producto cartesiano que puede devolver millones de filas incorrectas
- Usá alias de tabla siempre — especialmente con más de dos tablas; evita ambigüedad y hace el código más legible
- INNER JOIN cuando necesitás datos de ambas tablas — es el tipo más común y eficiente
- LEFT JOIN cuando necesitás incluir registros sin relación — y usá
WHERE columna IS NULLpara encontrar los "solos" - GROUP BY en la misma query que el JOIN — podés combinarlos libremente para armar reportes completos