lección 7
JOINs: cruzar tablas para responder preguntas complejas
INNER JOIN y LEFT JOIN — combinar datos de varias tablas en una sola consulta.
⏱ 60 min
Hasta ahora has trabajado con una tabla a la vez. Pero los datos reales están NORMALIZADOS: la información del cliente está en una tabla, sus pedidos en otra, los productos en otra. ¿Por qué? Porque repetir "Ana García, Madrid, ana@email.com" en cada uno de sus 50 pedidos es desperdiciar espacio y crear inconsistencias. En vez de eso, cada pedido guarda solo el cliente_id y la tabla clientes tiene el detalle.
Los JOINs son la forma de volver a UNIR esas tablas separadas cuando necesitas responder preguntas que cruzan datos: "¿Qué clientes de Madrid han comprado productos de Electrónica?" requiere cruzar clientes + pedidos + productos. Es como juntar dos hojas de Excel que comparten una columna en común.
### Nuestro esquema: las tablas que vas a cruzar
Antes de empezar con la sintaxis, aquí tienes el mapa completo. Estas son las tablas que usas en los ejercicios y cómo se conectan entre sí:
Consejo de senior: cuando llegas a un proyecto nuevo, lo primero que pido es el diagrama ER. Sin él estás adivinando qué tablas existen y cómo se conectan. Guarda este diagrama a mano — te va a servir para todos los ejercicios de esta lección y las siguientes.
### La analogía de las dos listas
Imagina que tienes dos listas: una con los nombres de todos los empleados de la empresa y otra con los emails corporativos asignados. Ambas tienen una columna en común: el ID del empleado. Un JOIN "engancha" las filas de ambas listas por ese ID compartido. Si un empleado aparece en ambas listas, se une. Si solo está en una... depende del tipo de JOIN.
### INNER JOIN: solo las coincidencias
INNER JOIN (o simplemente JOIN) devuelve SOLO las filas donde hay coincidencia en ambas tablas. Si un cliente no tiene pedidos, desaparece. Si un pedido referencia un cliente que no existe, también desaparece. Mucha gente lo explica como "la intersección del diagrama de Venn" — y hasta cierto punto vale. Pero hay una trampa que te pillará tarde o temprano: un JOIN puede MULTIPLICAR filas. Si un cliente tiene 10 pedidos, el resultado tendrá 10 filas para ese cliente. No es un filtro, es un cruce. La primera vez que me pasó, pensé que la query estaba mal. No lo estaba — es así como funciona.
1-- Sintaxis del INNER JOIN2SELECT c.nombre, c.ciudad, p.importe, p.fecha3FROM clientes c4INNER JOIN pedidos p ON p.cliente_id = c.id5ORDER BY p.fecha DESC6LIMIT 10;78-- "JOIN" sin prefijo es lo mismo que "INNER JOIN"9SELECT c.nombre, p.importe10FROM clientes c11JOIN pedidos p ON p.cliente_id = c.id12LIMIT 5;
INNER JOIN: la cláusula ON define la condición de unión
### La anatomía de un JOIN
- FROM tabla_izquierda alias — la primera tabla
- JOIN tabla_derecha alias — la segunda tabla que quieres cruzar
- ON condicion — cómo se relacionan (normalmente FK = PK)
- Los alias (c, p) son esenciales para indicar de qué tabla viene cada columna
- Puedes encadenar múltiples JOINs: FROM a JOIN b ON... JOIN c ON...
### LEFT JOIN: preservar la tabla izquierda
LEFT JOIN devuelve TODAS las filas de la tabla izquierda (la del FROM), y las coincidencias de la derecha. Si no hay coincidencia, las columnas de la tabla derecha vienen como NULL. Esto es crucial para preguntas como "¿qué clientes NO han comprado nunca?".
1-- LEFT JOIN: todos los clientes, incluso sin pedidos2SELECT c.nombre, c.ciudad, COUNT(p.id) AS pedidos3FROM clientes c4LEFT JOIN pedidos p ON p.cliente_id = c.id5GROUP BY c.nombre, c.ciudad6ORDER BY pedidos ASC7LIMIT 10;89-- Encontrar clientes que NUNCA han comprado10SELECT c.nombre, c.email, c.ciudad11FROM clientes c12LEFT JOIN pedidos p ON p.cliente_id = c.id13WHERE p.id IS NULL;
LEFT JOIN + WHERE ... IS NULL = encontrar los que NO coinciden
El patrón "LEFT JOIN + WHERE columna_derecha IS NULL" es un anti-join: encuentra lo que NO existe en la otra tabla. Es el equivalente de "buscar lo que falta". Úsalo para encontrar clientes sin pedidos, productos sin ventas, empleados sin departamento...
### JOINs con agregación: el combo más poderoso
El verdadero poder aparece cuando combinas JOINs con GROUP BY: "gasto total por ciudad" requiere JOIN (para tener la ciudad del cliente) + SUM + GROUP BY. Este combo es probablemente la consulta más frecuente en cualquier empresa:
1-- Ventas totales por ciudad (JOIN + GROUP BY)2SELECT3 c.ciudad,4 COUNT(p.id) AS pedidos,5 SUM(p.importe)::INTEGER AS total,6 ROUND(AVG(p.importe), 2) AS ticket_medio7FROM clientes c8JOIN pedidos p ON p.cliente_id = c.id9WHERE p.estado = 'completado'10GROUP BY c.ciudad11ORDER BY total DESC;1213-- Categoría más vendida (JOIN + GROUP BY con categorias)14SELECT15 cat.nombre AS categoria,16 COUNT(p.id) AS pedidos,17 SUM(p.importe)::INTEGER AS total18FROM pedidos p19JOIN productos pr ON pr.id = p.producto_id20JOIN categorias cat ON cat.id = pr.categoria_id21WHERE p.estado = 'completado'22GROUP BY cat.nombre23ORDER BY total DESC;
JOIN + GROUP BY: cruzar datos de varias tablas y luego agregar
Cuando haces JOIN y luego COUNT(*), cuidado: si un cliente tiene 3 pedidos, aparece 3 veces ANTES del GROUP BY. COUNT(*) contará esas 3 filas. Si quieres contar clientes únicos, usa COUNT(DISTINCT c.id).
### Errores comunes en JOINs
- Olvidar el ON: sin condición de unión, obtienes un CROSS JOIN (producto cartesiano — millones de filas basura)
- Condición ON incorrecta: si pones ON p.id = c.id en vez de ON p.cliente_id = c.id, cruzas por los IDs equivocados
- No usar alias: sin alias, DuckDB no sabe si "id" viene de clientes o de pedidos
- LEFT JOIN pero filtrar la tabla derecha en WHERE: WHERE p.estado = 'completado' ANULA el LEFT JOIN (convierte NULLs en no-coincidencia)
¿Y RIGHT JOIN? Existe, pero en la práctica todo RIGHT JOIN se puede reescribir como LEFT JOIN invirtiendo el orden de las tablas. Por eso casi nadie lo usa: es más legible poner la tabla "principal" a la izquierda y hacer LEFT JOIN con la secundaria. Si lo ves en código legacy, ya sabes qué hace — pero no lo necesitas.
## ejercicios
Clientes con sus pedidos recientes
El equipo de atención al cliente necesita ver los 10 pedidos más recientes con el nombre del cliente, la ciudad, la fecha y el importe. Ordena por fecha descendente.
Clientes que nunca han comprado
Marketing quiere identificar clientes registrados que no han hecho ningún pedido para enviarles una campaña de activación. Usa LEFT JOIN para encontrarlos. Muestra nombre, email y ciudad.
Productos más vendidos
El jefe de compras quiere saber los 10 productos más vendidos (por número de pedidos completados). Muestra nombre del producto, categoría y número de ventas.
Gasto total por cliente con nombre
Finanzas quiere ver los 15 clientes con mayor gasto total (solo completados). Necesitan: nombre del cliente, ciudad, número de pedidos y gasto total. Requiere JOIN para traer el nombre.
Ventas por categoría de producto
El equipo de producto quiere un informe de rendimiento por categoría. Muestra: categoría, total de pedidos, facturación total y ticket medio. Solo pedidos completados. Ordena por facturación.
Regístrate para guardar tu progreso.
## comentarios
Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.
Inicia sesión para comentar y responder.
cargando comentarios...