Saltar al contenido

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í:

Las flechas muestran las FK: pedidos.cliente_id apunta a clientes.id, pedidos.producto_id a productos.id, etc.

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 excluye lo que no coincide. LEFT JOIN preserva todo el lado izquierdo.

### 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 JOIN
2SELECT c.nombre, c.ciudad, p.importe, p.fecha
3FROM clientes c
4INNER JOIN pedidos p ON p.cliente_id = c.id
5ORDER BY p.fecha DESC
6LIMIT 10;
7
8-- "JOIN" sin prefijo es lo mismo que "INNER JOIN"
9SELECT c.nombre, p.importe
10FROM clientes c
11JOIN pedidos p ON p.cliente_id = c.id
12LIMIT 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 pedidos
2SELECT c.nombre, c.ciudad, COUNT(p.id) AS pedidos
3FROM clientes c
4LEFT JOIN pedidos p ON p.cliente_id = c.id
5GROUP BY c.nombre, c.ciudad
6ORDER BY pedidos ASC
7LIMIT 10;
8
9-- Encontrar clientes que NUNCA han comprado
10SELECT c.nombre, c.email, c.ciudad
11FROM clientes c
12LEFT JOIN pedidos p ON p.cliente_id = c.id
13WHERE 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)
2SELECT
3 c.ciudad,
4 COUNT(p.id) AS pedidos,
5 SUM(p.importe)::INTEGER AS total,
6 ROUND(AVG(p.importe), 2) AS ticket_medio
7FROM clientes c
8JOIN pedidos p ON p.cliente_id = c.id
9WHERE p.estado = 'completado'
10GROUP BY c.ciudad
11ORDER BY total DESC;
12
13-- Categoría más vendida (JOIN + GROUP BY con categorias)
14SELECT
15 cat.nombre AS categoria,
16 COUNT(p.id) AS pedidos,
17 SUM(p.importe)::INTEGER AS total
18FROM pedidos p
19JOIN productos pr ON pr.id = p.producto_id
20JOIN categorias cat ON cat.id = pr.categoria_id
21WHERE p.estado = 'completado'
22GROUP BY cat.nombre
23ORDER 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

[01]

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.

Cargando editor...
[02]

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.

Cargando editor...
[03]

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.

Cargando editor...
[04]

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.

Cargando editor...
[05]

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.

Cargando editor...

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...