Saltar al contenido

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 + categorias
2SELECT
3 c.nombre AS cliente,
4 c.ciudad,
5 pr.nombre AS producto,
6 cat.nombre AS categoria,
7 p.importe,
8 p.fecha
9FROM pedidos p
10JOIN clientes c ON c.id = p.cliente_id
11JOIN productos pr ON pr.id = p.producto_id
12JOIN categorias cat ON cat.id = pr.categoria_id
13WHERE p.estado = 'completado'
14ORDER BY p.importe DESC
15LIMIT 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:

La tabla pedidos es el puente: conecta clientes con productos

### 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.precio
3FROM productos pr
4JOIN categorias cat ON cat.id = pr.categoria_id
5LEFT JOIN pedidos p ON p.producto_id = pr.id
6WHERE p.id IS NULL;
7
8-- Clientes VIP que no han comprado en 2024
9SELECT c.nombre, c.email, c.ciudad
10FROM clientes c
11LEFT JOIN pedidos p
12 ON p.cliente_id = c.id
13 AND p.fecha >= '2024-01-01'
14WHERE c.es_vip = TRUE
15 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 VIP
2SELECT DISTINCT c1.nombre, c1.ciudad
3FROM clientes c1
4JOIN clientes c2
5 ON c1.ciudad = c2.ciudad
6 AND c2.es_vip = TRUE
7 AND c1.id != c2.id
8WHERE c1.es_vip = FALSE
9LIMIT 20;
10
11-- Vendedores que venden más que el promedio de su equipo
12SELECT v1.nombre, v1.equipo, v1.ventas
13FROM vendedores v1
14JOIN (
15 SELECT equipo, AVG(ventas) AS media
16 FROM vendedores
17 GROUP BY equipo
18) v2 ON v1.equipo = v2.equipo
19WHERE 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ía
2SELECT
3 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.fecha
10FROM pedidos p
11JOIN clientes c ON c.id = p.cliente_id
12JOIN productos pr ON pr.id = p.producto_id
13JOIN categorias cat ON cat.id = pr.categoria_id
14WHERE p.estado = 'completado'
15 AND c.es_vip = TRUE
16ORDER BY p.importe DESC
17LIMIT 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 × ciudad
2-- Ú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 pedidos
6 WHERE fecha >= '2024-01-01'
7),
8ciudades AS (
9 SELECT DISTINCT ciudad FROM clientes
10 WHERE ciudad IS NOT NULL
11)
12SELECT f.fecha, c.ciudad
13FROM fechas f
14CROSS JOIN ciudades c
15ORDER 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

[01]

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.

Cargando editor...
[02]

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.

Cargando editor...
[03]

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.

Cargando editor...
[04]

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.

Cargando editor...
[05]

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

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