Pregunta 1¿Cuál de las siguientes describe correctamente una CTE en WITH nombre AS (SELECT ...) SELECT ... FROM nombre;?
Cláusula WITH — Organiza tu consulta con resultados intermedios con nombre
Este artículo forma parte del Curso de SQL, con el que dominarás desde cero habilidades prácticas de SQL, desde lo básico hasta las consultas complejas y la optimización de SQL.
Aprende la cláusula SQL WITH (CTE): construye una CTE high_earner, hazle JOIN con department y luego encadena dept_avg → top_earner para una agregación en varias etapas sobre datos de empleados.
Datos usados en este artículo — employee y department
Una CTE (Common Table Expression) es un mecanismo en el que escribes WITH nombre AS (SELECT ...) para darle un nombre a una subconsulta y luego referencias ese nombre en las consultas que siguen.
Como puedes dividir una subconsulta muy anidada en etapas, tu código se vuelve mucho más fácil de leer.
El material es la tabla employee (30 filas; la columna manager_id apunta al emp_id del manager, una estructura autorreferencial) y la tabla department (6 filas).
Trabajarás ejercicios que usan CTEs para escribir agregaciones en varias etapas de forma más legible.
Construye un resultado intermedio con nombre usando WITH — haz legibles las consultas de varias etapas
Cuando escribes WITH nombre AS (SELECT ...), el resultado del SELECT interno recibe un nombre y puedes referenciarlo desde FROM o JOIN en la consulta principal que sigue.
En lugar de escribir el proceso de dos pasos «primero construir un resultado intermedio y luego usarlo para producir el resultado final» como una subconsulta anidada, puedes partirlo en una forma que se lee de arriba abajo.
Una CTE es un nombre temporal que desaparece en cuanto termina la consulta principal — no crea una tabla real.
La sintaxis es WITH nombre AS ( ... ) SELECT ... FROM nombre ...;.
-- Agrupa a los high earners en la CTE high_earner,
-- luego haz join con department para adjuntar el nombre del departamento
WITH high_earner AS (
SELECT emp_id, name, dept_id, salary
FROM employee
WHERE salary >= 7000000
)
SELECT h.name, h.salary, d.dept_name
FROM high_earner h
LEFT JOIN department d ON h.dept_id = d.dept_id
ORDER BY h.salary DESC;
En el código de arriba, el nombre high_earner aísla el resultado intermedio «empleados con salary >= 7.000.000» y la consulta principal lo referencia como una tabla con FROM high_earner.
Así se ve ese flujo como diagrama.
Puedes escribir el mismo procesamiento con una subconsulta (un SELECT anidado dentro de los paréntesis de otro SELECT).
Reconstruyamos la versión anterior con WITH como una subconsulta en lugar de una CTE.
-- El mismo procesamiento escrito como subconsulta (tabla derivada)
-- Un SELECT vive dentro de los paréntesis de FROM, así que lees exterior → interior → exterior
SELECT h.name, h.salary, d.dept_name
FROM (
SELECT emp_id, name, dept_id, salary
FROM employee
WHERE salary >= 7000000
) AS h
LEFT JOIN department d ON h.dept_id = d.dept_id
ORDER BY h.salary DESC;
El resultado es exactamente el mismo que la versión con WITH, pero con la versión con subconsulta tienes que meterte dentro de un SELECT anidado en FROM para entender qué hace el SELECT interno.
La versión con WITH aísla el resultado intermedio con el nombre `high_earner`, así que las dos etapas «① filtrar a los high earners → ② adjuntar el nombre del departamento» se leen directamente de arriba abajo.
La diferencia crece a medida que añades más etapas intermedias — para cuando tienes 3 o 4 etapas, las subconsultas muy anidadas se vuelven difíciles de seguir.
Encadena varias cláusulas WITH — divide la agregación en varias etapas, una a una
Tras WITH puedes listar varias CTEs separadas por comas.
La forma es WITH a AS (...), b AS (...) SELECT ..., y una CTE posterior puede referenciar a una anterior.
Esto te permite dividir una agregación en varias etapas («calcular la media por departamento» → «elegir empleados por encima de esa media» → «adjuntar el nombre del departamento») en etapas con nombre, una a una, de forma que incluso en una consulta larga puedes mantener separado el rol de cada etapa.
-- ① dept_avg: salario medio por departamento
-- ② top_earner: empleados por encima de la media de su propio departamento
-- ③ Consulta principal: adjunta el nombre del departamento y muestra
WITH dept_avg AS (
SELECT dept_id, AVG(salary) AS avg_salary
FROM employee
WHERE dept_id IS NOT NULL
GROUP BY dept_id
),
top_earner AS (
SELECT e.name, e.dept_id, e.salary
FROM employee e
JOIN dept_avg da ON e.dept_id = da.dept_id
WHERE e.salary > da.avg_salary
)
SELECT d.dept_name, t.name, t.salary
FROM top_earner t
JOIN department d ON t.dept_id = d.dept_id
ORDER BY d.dept_name, t.salary DESC;
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Comparado con SELECT h.name FROM (SELECT name FROM employee WHERE salary >= 7000000) AS h JOIN department d ON h.dept_id = d.dept_id;, ¿cuál es la diferencia al reescribir el mismo procesamiento con una CTE WITH?
Pregunta 3¿Cuál de las siguientes describe correctamente listar varias CTEs como WITH a AS (...), b AS (...) SELECT ... FROM b;?