Aprende SQL · lección gratuita
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.
WITH a AS (...), b AS (...), c AS (...) SELECT ... — una sola palabra WITH, las CTEs separadas por coma.b puede leer de a, y c puede leer de a y b; el orden de declaración importa (una CTE solo ve a las definidas antes).lineas, por_cliente, ranking), lo que facilita depurar paso a paso.SELECT dentro de otro SELECT dentro de otro se aplana en una secuencia lineal.SELECT principal, que normalmente lee de la última etapa.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
-- 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, nopaso2). Una cadena bien nombrada se lee casi como prosa y se depura cambiando solo elSELECTfinal.
| Cláusula / Función | Qué hace |
|---|---|
WITH a AS (...), b AS (...) | Varias CTEs en una sola cláusula WITH |
b lee de a | Una CTE puede referenciar las declaradas antes |
| Sin coma final | No hay coma entre la última CTE y el SELECT principal |
| Depurar | Reemplaza el SELECT final por SELECT * FROM etapa |
---