Una subconsulta es una consulta anidada dentro de otra. Un CTE (common table expression, o expresión de tabla común) es esa misma consulta interna movida al inicio de la sentencia con WITH y con un nombre, para que una consulta larga se lea como una serie de pasos. Los ejemplos funcionan en PostgreSQL, MySQL, SQLite y SQL Server.
Cómo usar CTEs y subconsultas
Una subconsulta va donde se usa: en FROM actúa como una tabla, y en WHERE o SELECT aporta un valor o una lista. Un CTE se define primero y luego la consulta que sigue lo usa como si fuera una tabla:
-- Subquery: written where it's used
SELECT columns FROM (SELECT ...) AS name;
-- CTE: named first, then used like a table
WITH name AS (SELECT ...)
SELECT columns FROM name;
SELECT n * 2 AS doubled FROM (SELECT 21 AS n) AS numbers;
-- Output: 42
WITH numbers AS (SELECT 21 AS n)
SELECT n * 2 AS doubled FROM numbers;
-- Output: 42
En una tabla real, las dos formas devuelven las mismas filas. Aquí tienes un mismo filtro escrito de ambas maneras:
-- Input: people
-- first_name | last_name | job | favorite_food
-- -----------+-----------+----------+--------------
-- Matthew | Ortiz | Chef | cheese
-- Matt | Novak | Pilot | pizza
-- Mattie | Brooks | Engineer | cheese
-- Priya | Nair | Teacher | cheese
-- Subquery
SELECT *
FROM (SELECT first_name, last_name, job FROM people WHERE favorite_food = 'cheese') AS cheese_lovers
WHERE first_name LIKE 'Matt%';
-- CTE
WITH cheese_lovers AS (
SELECT first_name, last_name, job FROM people WHERE favorite_food = 'cheese'
)
SELECT * FROM cheese_lovers
WHERE first_name LIKE 'Matt%';
-- Output of both:
-- first_name | last_name | job
-- -----------+-----------+---------
-- Matthew | Ortiz | Chef
-- Mattie | Brooks | Engineer
Reglas que debes conocer
-
Un CTE solo dura una sentencia. La consulta justo después de
WITHpuede usarlo; la siguiente sentencia no. Para conservar un resultado en varias consultas, usa una tabla temporal o una vista. -
Ponle nombre a una subconsulta en
FROM, comoAS numbersarriba. MySQL y SQL Server la rechazan si no lo tiene. - Una subconsulta usada como un solo valor debe devolver una fila:
SELECT (SELECT 1 UNION ALL SELECT 2) AS n;
-- ERROR: more than one row returned by a subquery used as an expression
-- (SQLite doesn't complain: it quietly uses the first row, 1)
- La velocidad suele ser la misma. En general, las bases de datos planifican un CTE que se usa una sola vez igual que la subconsulta equivalente, así que elige la que se lea mejor.
Sintaxis alternativa
Un CTE que se refiere a sí mismo (ver abajo) se escribe WITH RECURSIVE:
-- In SQL Server, leave out RECURSIVE
WITH RECURSIVE counter AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM counter WHERE n < 3
)
SELECT n FROM counter;
-- Output:
-- n
-- -
-- 1
-- 2
-- 3
En SQL Server, la sentencia anterior a un WITH debe terminar con punto y coma, por eso el código T-SQL suele iniciar un CTE con ;WITH.
¿Cuándo usar un CTE en lugar de una subconsulta?
Una subconsulta está bien para un paso pequeño. Cámbiate a un CTE cuando la consulta tenga varios pasos, necesite el mismo resultado dos veces o tenga que referirse a sí misma.
Filtrar contra un valor calculado
Cuando la consulta interna solo produce un valor o una lista para comparar, como el total promedio de los pedidos, una subconsulta en WHERE es la forma más corta de escribirla. Un CTE también funcionaría, pero agrega un nombre que nadie necesita.
Dividir una consulta larga en pasos con nombre
Cuando una consulta tiene etapas (total por cliente, luego los primeros, luego la lista final), ponle nombre a cada etapa. Lista varios CTEs después de un solo WITH, separados por comas: cada uno puede usar los anteriores, y la consulta se lee de arriba hacia abajo en lugar de adentro hacia afuera.
Usar el mismo resultado más de una vez
Una sentencia puede referirse a un CTE todas las veces que quiera, así que un resultado que necesitas dos veces (una lista de totales y su promedio, o cada total y el total general) se escribe una sola vez. Con subconsultas tendrías que copiar toda la consulta interna.
Recorrer una jerarquía
Solo un CTE puede referirse a sí mismo. Un CTE recursivo parte de algunas filas y sigue uniéndose a la tabla hasta que no aparecen filas nuevas; así recorres un organigrama o un árbol de categorías, o generas una serie de números o fechas.
Ejemplos reales de CTEs y subconsultas
Productos con precio superior al promedio de su categoría
La subconsulta se refiere a p.category de la consulta externa, así que se calcula para la categoría de cada producto (una subconsulta correlacionada):
-- Input: products
-- name | category | price
-- --------------+-----------+-------
-- Desk Lamp | Lighting | 24.99
-- Floor Lamp | Lighting | 89.00
-- Office Chair | Furniture | 149.00
-- Standing Desk | Furniture | 399.00
-- Bookshelf | Furniture | 120.00
SELECT name, category, price
FROM products p
WHERE price > (
SELECT AVG(price) FROM products WHERE category = p.category
)
ORDER BY category, name;
-- Output:
-- name | category | price
-- --------------+-----------+-------
-- Standing Desk | Furniture | 399.00
-- Floor Lamp | Lighting | 89.00
El producto más vendido de cada categoría
Tres pasos, cada uno con nombre: suma las unidades por producto, clasifica los productos dentro de cada categoría y luego quédate con el primero:
-- Input: sales
-- id | product | category | units
-- ---+--------------+-----------+------
-- 1 | Desk Lamp | Lighting | 3
-- 2 | Floor Lamp | Lighting | 5
-- 3 | Desk Lamp | Lighting | 4
-- 4 | Office Chair | Furniture | 2
-- 5 | Bookshelf | Furniture | 6
WITH product_totals AS (
SELECT category, product, SUM(units) AS units
FROM sales
GROUP BY category, product
),
ranked AS (
SELECT category, product, units,
RANK() OVER (PARTITION BY category ORDER BY units DESC) AS place
FROM product_totals
)
SELECT category, product, units
FROM ranked
WHERE place = 1
ORDER BY category;
-- Output:
-- category | product | units
-- ----------+-----------+------
-- Furniture | Bookshelf | 6
-- Lighting | Desk Lamp | 7
La participación de cada región en las ventas
region_totals se usa dos veces: una para las filas y otra dentro de una subconsulta para el total general:
-- Input: orders
-- id | region | amount
-- ---+--------+--------
-- 1 | North | 1200.00
-- 2 | South | 450.00
-- 3 | North | 300.00
-- 4 | West | 800.00
-- 5 | South | 250.00
WITH region_totals AS (
SELECT region, SUM(amount) AS total
FROM orders
GROUP BY region
)
SELECT
region,
total,
ROUND(100.0 * total / (SELECT SUM(total) FROM region_totals), 1) AS pct_of_sales
FROM region_totals
ORDER BY total DESC;
-- Output:
-- region | total | pct_of_sales
-- -------+---------+-------------
-- North | 1500.00 | 50.0
-- West | 800.00 | 26.7
-- South | 700.00 | 23.3
Escrita con subconsultas, la consulta con GROUP BY aparecería dos veces.
La cadena de jefes de un empleado
Empieza desde un empleado y sigue manager_id hacia arriba hasta que se acabe:
-- Input: employees
-- id | name | manager_id
-- ---+-----------+-----------
-- 1 | Maya Chen | NULL
-- 2 | Sam Ortiz | 1
-- 3 | Jo Kim | 2
-- 4 | Lee Ross | 2
-- 5 | Ana Diaz | 1
-- In SQL Server, leave out RECURSIVE
WITH RECURSIVE chain AS (
SELECT id, name, manager_id, 0 AS level
FROM employees
WHERE name = 'Jo Kim'
UNION ALL
SELECT e.id, e.name, e.manager_id, chain.level + 1
FROM employees e
JOIN chain ON e.id = chain.manager_id
)
SELECT level, name FROM chain ORDER BY level;
-- Output:
-- level | name
-- ------+----------
-- 0 | Jo Kim
-- 1 | Sam Ortiz
-- 2 | Maya Chen
Más lecturas sobre CTEs y subconsultas
En este blog:
- Guía de tablas temporales en SQL: conserva un resultado intermedio para más de una sentencia
-
Guía para encontrar duplicados en SQL: CTEs con
ROW_NUMBER()para detectar filas repetidas - Guía para eliminar duplicados en SQL: borrar duplicados mediante un CTE
- Guía de GROUP BY en SQL: las agregaciones con las que se construyen la mayoría de los CTEs
- Guía de JOIN en SQL con diagramas: unir un CTE con otras tablas
Documentación oficial:
- PostgreSQL: consultas WITH y expresiones de subconsulta
- MySQL: WITH (expresiones de tabla comunes) y subconsultas
- SQLite: la cláusula WITH
- SQL Server: WITH common_table_expression y subconsultas
Beekeeper Studio es un cliente de bases de datos gratuito y de código abierto
La mejor herramienta de consultas y edición de SQL que he usado. Tiene todo lo que necesito para administrar mi base de datos. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio es rápido, intuitivo y fácil de usar. Es compatible con muchísimas bases de datos y funciona de maravilla en Windows, Mac y Linux.
Lo que dicen los usuarios de Beekeeper Studio
"Beekeeper Studio reemplazó por completo mi antiguo flujo de trabajo con SQL. Es rápido, intuitivo y hace que trabajar con bases de datos vuelva a ser agradable."
"He probado muchas GUI de bases de datos, pero Beekeeper logra el equilibrio perfecto entre funciones y simplicidad. Simplemente funciona."