lección 8
JOINs múltiples y tipos avanzados
Cruzar 3+ tablas, self-joins, anti-joins y CROSS JOIN — consultas que reflejan modelos de datos reales.
⏱ 55 min
En la lección anterior cruzaste 2 tablas. En la realidad, rara vez es suficiente. Para responder "¿qué categoría de producto compran más los clientes VIP de Madrid?" necesitas cruzar clientes → pedidos → productos. Tres tablas en una query. Para "facturación por categoría y ciudad" necesitas cuatro. Hoy vas a encadenar JOINs como un profesional y a dominar patrones avanzados como el anti-join y el self-join.
La analogía: un JOIN de 2 tablas es como juntar dos piezas de un puzzle. Un JOIN de 3 o más tablas es como armar el puzzle completo — cada pieza se conecta con otra usando la columna que comparten. La clave es pensar en CADENAS: clientes se conecta con pedidos por cliente_id, pedidos se conecta con productos por producto_id. Cada JOIN añade una tabla más a la cadena.
### Encadenar JOINs: de 2 a N tablas
La sintaxis es simple: después del primer JOIN, añades otro. Y otro. Cada uno con su propia cláusula ON que define cómo se conecta la nueva tabla con las que ya están en la query:
1-- JOIN de 3 tablas: pedidos → clientes + productos + categorias2SELECT3 c.nombre AS cliente,4 c.ciudad,5 pr.nombre AS producto,6 cat.nombre AS categoria,7 p.importe,8 p.fecha9FROM pedidos p10JOIN clientes c ON c.id = p.cliente_id11JOIN productos pr ON pr.id = p.producto_id12JOIN categorias cat ON cat.id = pr.categoria_id13WHERE p.estado = 'completado'14ORDER BY p.importe DESC15LIMIT 10;
Cuatro tablas: pedidos como "tabla puente" entre clientes, productos y categorías
Consejo de senior: cuando encadenas muchos JOINs, empieza por la tabla que tiene las claves foráneas (la tabla "puente"). Pedidos es perfecta como punto de partida porque tiene cliente_id Y producto_id — conecta con las otras dos directamente.
### El modelo mental: la tabla puente
En modelos normalizados, hay tablas que actúan como "puentes" entre otras. La tabla pedidos conecta clientes con productos. La tabla lineas_pedido conecta pedidos con productos a nivel de detalle. Identificar la tabla puente es el primer paso para escribir un JOIN multi-tabla:
### Anti-join: encontrar lo que NO existe
Uno de los patrones más útiles en SQL empresarial es encontrar lo que FALTA: productos que nadie ha comprado, clientes que no han vuelto desde hace 6 meses, categorías sin stock. El patrón es LEFT JOIN + WHERE ... IS NULL:
1-- Productos que NADIE ha comprado (anti-join)2SELECT pr.nombre, cat.nombre AS categoria, pr.precio3FROM productos pr4JOIN categorias cat ON cat.id = pr.categoria_id5LEFT JOIN pedidos p ON p.producto_id = pr.id6WHERE p.id IS NULL;78-- Clientes VIP que no han comprado en 20249SELECT c.nombre, c.email, c.ciudad10FROM clientes c11LEFT JOIN pedidos p12 ON p.cliente_id = c.id13 AND p.fecha >= '2024-01-01'14WHERE c.es_vip = TRUE15 AND p.id IS NULL;
Anti-join: LEFT JOIN + IS NULL = "lo que no existe en la otra tabla"
Cuidado con dónde pones el filtro en un LEFT JOIN. Si filtras la tabla derecha en el WHERE (WHERE p.estado = 'completado'), conviertes el LEFT JOIN en un INNER JOIN implícito (los NULLs no pasan el filtro). Mueve esa condición al ON para mantener el LEFT JOIN.
### Self-join: una tabla consigo misma
A veces necesitas comparar filas de la misma tabla entre sí. "Clientes que viven en la misma ciudad que otro cliente VIP", "empleados que ganan más que su jefe". Se hace con un self-join: la tabla aparece dos veces con alias diferentes:
1-- Clientes que están en la misma ciudad que algún VIP2SELECT DISTINCT c1.nombre, c1.ciudad3FROM clientes c14JOIN clientes c25 ON c1.ciudad = c2.ciudad6 AND c2.es_vip = TRUE7 AND c1.id != c2.id8WHERE c1.es_vip = FALSE9LIMIT 20;1011-- Vendedores que venden más que el promedio de su equipo12SELECT v1.nombre, v1.equipo, v1.ventas13FROM vendedores v114JOIN (15 SELECT equipo, AVG(ventas) AS media16 FROM vendedores17 GROUP BY equipo18) v2 ON v1.equipo = v2.equipo19WHERE v1.ventas > v2.media;
Self-join: misma tabla, dos aliases, comparación cruzada
### JOIN con 4 tablas: el ejemplo real completo
Un informe real suele necesitar datos de 3-5 tablas simultáneamente. Aquí un ejemplo completo que cruza pedidos, clientes, productos y categorías para generar un informe de ventas detallado:
1-- Informe completo: cliente + producto + categoría2SELECT3 c.nombre AS cliente,4 c.ciudad,5 pr.nombre AS producto,6 cat.nombre AS categoria,7 p.cantidad,8 p.importe,9 p.fecha10FROM pedidos p11JOIN clientes c ON c.id = p.cliente_id12JOIN productos pr ON pr.id = p.producto_id13JOIN categorias cat ON cat.id = pr.categoria_id14WHERE p.estado = 'completado'15 AND c.es_vip = TRUE16ORDER BY p.importe DESC17LIMIT 15;
4 tablas en una query: pedidos como puente central
Cuando escribas JOINs de 3+ tablas, dibuja mentalmente (o en papel) las relaciones: ¿qué tabla se conecta con cuál? ¿Por qué columna? Esto te ahorra errores. En mi primer año, perdí 2 horas porque estaba haciendo JOIN por el ID equivocado — los resultados "parecían bien" pero estaban completamente mal.
### CROSS JOIN: generar todas las combinaciones
CROSS JOIN es diferente a los demás: NO tiene cláusula ON. Genera el producto cartesiano — todas las combinaciones posibles entre las filas de ambas tablas. Si la tabla A tiene 10 filas y la B tiene 365 filas, el resultado tiene 3.650 filas (10 × 365). Suena peligroso (y lo es si lo haces sin querer), pero tiene casos de uso legítimos.
1-- Generar todas las combinaciones fecha × ciudad2-- Útil para reportes: necesitas una fila por cada día×ciudad,3-- aunque ese día no haya habido pedidos en esa ciudad (para poner 0)4WITH fechas AS (5 SELECT DISTINCT fecha FROM pedidos6 WHERE fecha >= '2024-01-01'7),8ciudades AS (9 SELECT DISTINCT ciudad FROM clientes10 WHERE ciudad IS NOT NULL11)12SELECT f.fecha, c.ciudad13FROM fechas f14CROSS JOIN ciudades c15ORDER BY f.fecha, c.ciudad;
CROSS JOIN: generar el "esqueleto" de un reporte con todas las combinaciones posibles
Cuidado al cruzar cabecera con detalle. Si unes pedidos con lineas_pedido, cada pedido se repite una vez por cada línea que tenga. Sumar p.importe después de ese JOIN te da una cifra inflada — y no salta ningún error. Regla: si necesitas métricas de la cabecera (pedidos) y del detalle (lineas_pedido) a la vez, agrega cada nivel por separado y luego cruza los resultados.
## ejercicios
Detalle completo de pedidos VIP
Genera un informe con los 20 pedidos más recientes de clientes VIP: nombre del cliente, ciudad, nombre del producto, categoría, importe y fecha. Requiere JOIN de 4 tablas.
Productos sin ninguna venta
El jefe de compras quiere saber qué productos del catálogo no se han vendido nunca (no aparecen en ningún pedido). Muestra nombre, categoría y precio. Usa el patrón anti-join.
Categorías más vendidas por ciudad
El equipo de estrategia quiere saber qué categoría vende más en cada ciudad (por importe total, solo completados). Cruza pedidos + clientes + productos + categorias. Agrupa por ciudad y categoría.
Clientes en la misma ciudad que un VIP
Marketing quiere enviar campañas a clientes NO-VIP que viven en ciudades donde hay al menos un VIP (potencial de upgrade). Usa self-join para encontrarlos. Muestra nombre, ciudad y email. Limita a 20 resultados.
Detalle con líneas de pedido
Genera un informe detallado usando la tabla lineas_pedido: para los 10 productos con mayor facturación en líneas de pedido, muestra nombre del producto, categoría, total de unidades vendidas (SUM cantidad) y facturación total (SUM cantidad * precio_unitario).
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...