Aprende SQL · lección gratuita

Lección 27 · CTEs encadenados

Resumen

Una sola cláusula WITH puede definir varias CTEs separadas por comas, y cada CTE posterior puede usar las anteriores. Esto permite construir una consulta por etapas: una CTE calcula datos crudos, la siguiente los agrega, la tercera los filtra o rankea. El resultado es una pipeline legible de transformaciones, equivalente a encadenar pasos de un proceso analítico sin recurrir a subconsultas anidadas profundas.

Sintaxis / Conceptos

El patrón canónico es: CTE 1 = normalización / cálculo de líneas; CTE 2 = agregación sobre la CTE 1; CTE 3 = ranking o filtro sobre la CTE 2; consulta final = presentación. Solo se escribe WITH una vez; cada CTE adicional se separa con coma. No pongas coma después de la última CTE antes del SELECT principal: es un error de sintaxis típico.

El orden de ejecución conceptual sigue la cadena de dependencias, no el orden de cláusulas SQL clásico. Aun así, el optimizador es libre de fusionar etapas; las CTEs encadenadas son una herramienta de organización del pensamiento, no una garantía de materialización paso a paso.

Para depurar una cadena larga, reemplaza temporalmente el SELECT final por SELECT * FROM etapa_intermedia y verás exactamente qué produce esa etapa.

WITH RECURSIVE jerarquia AS (SELECT id, nombre, jefe_id, 0 AS nivel FROM empleados WHERE jefe_id = 0 UNION ALL SELECT e.id, e.nombre, e.jefe_id, j.nivel + 1 FROM empleados e JOIN jerarquia j ON e.jefe_id = j.id) SELECT nivel, nombre FROM jerarquia ORDER BY nivel, nombre LIMIT 8

Ejemplos

-- Pipeline de 3 etapas: línea → revenue por cliente → promedio por segmento
WITH linea AS (
  SELECT p.cliente_id,
         dp.cantidad * dp.precio_unitario * (1 - dp.descuento) AS rev
  FROM detalle_pedidos dp
  JOIN pedidos p ON p.id = dp.pedido_id
),
por_cliente AS (
  SELECT cliente_id, SUM(rev) AS total
  FROM linea
  GROUP BY cliente_id
)
SELECT c.segmento, ROUND(AVG(pc.total), 2) AS revenue_medio
FROM por_cliente pc
JOIN clientes c ON c.id = pc.cliente_id
GROUP BY c.segmento
ORDER BY revenue_medio DESC;
-- Cuatro etapas: revenue por producto, total global y porcentaje de contribución
WITH linea AS (
  SELECT dp.producto_id,
         dp.cantidad * dp.precio_unitario * (1 - dp.descuento) AS rev
  FROM detalle_pedidos dp
),
por_producto AS (
  SELECT producto_id, SUM(rev) AS total FROM linea GROUP BY producto_id
),
total_global AS (
  SELECT SUM(total) AS g FROM por_producto
)
SELECT pr.nombre,
       ROUND(pp.total, 0) AS revenue,
       ROUND(100.0 * pp.total / tg.g, 1) AS pct
FROM por_producto pp
JOIN productos pr ON pr.id = pp.producto_id
CROSS JOIN total_global tg
ORDER BY revenue DESC
LIMIT 5;
💡 Nombra cada CTE por lo que contiene, no por lo que hace (por_cliente, no paso2). Una cadena bien nombrada se lee casi como prosa y se depura cambiando solo el SELECT final.

Cheatsheet

Cláusula / FunciónQué hace
WITH a AS (...), b AS (...)Varias CTEs en una sola cláusula WITH
b lee de aUna CTE puede referenciar las declaradas antes
Sin coma finalNo hay coma entre la última CTE y el SELECT principal
DepurarReemplaza el SELECT final por SELECT * FROM etapa

---

← Expresiones de tabla común: WITHCTEs recursivos →

Ver todas las lecciones de Aprende SQL →