Uma subquery é uma consulta aninhada dentro de outra consulta. Uma CTE (common table expression) é essa mesma consulta interna movida para o topo da instrução com WITH e com um nome, para que uma consulta longa seja lida como uma série de etapas. Os exemplos funcionam em PostgreSQL, MySQL, SQLite e SQL Server.
Como usar CTEs e subqueries
Uma subquery fica onde é usada: no FROM ela funciona como uma tabela, e no WHERE ou SELECT ela fornece um valor ou uma lista. Uma CTE é definida primeiro e depois usada como uma tabela pela consulta que vem em seguida:
-- 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
Em uma tabela real, as duas formas retornam as mesmas linhas. Veja um filtro escrito das duas maneiras:
-- 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
Regras importantes
-
Uma CTE só dura uma instrução. A consulta logo após o
WITHpode usá-la; a instrução seguinte não. Para manter um resultado em várias consultas, use uma tabela temporária ou uma view. -
Dê um nome à subquery no
FROM, comoAS numbersacima. MySQL e SQL Server rejeitam a subquery sem nome. - Uma subquery usada como valor único deve retornar uma linha:
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)
- A velocidade costuma ser a mesma. Em geral, os bancos de dados planejam uma CTE usada uma única vez da mesma forma que a subquery equivalente, então escolha a que for mais legível.
Sintaxe alternativa
Uma CTE que faz referência a si mesma (veja abaixo) é escrita com 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
No SQL Server, a instrução antes de um WITH deve terminar com ponto e vírgula, e é por isso que código T-SQL muitas vezes começa uma CTE com ;WITH.
Quando usar uma CTE em vez de uma subquery?
Uma subquery funciona bem para uma etapa pequena. Mude para uma CTE quando a consulta tiver várias etapas, precisar do mesmo resultado duas vezes ou tiver que fazer referência a si mesma.
Filtrar por um valor calculado
Quando a consulta interna só produz um valor ou uma lista para comparação, como o total médio dos pedidos, uma subquery no WHERE é a forma mais curta de escrever. Uma CTE também funcionaria, mas adiciona um nome que ninguém precisa.
Dividir uma consulta longa em etapas nomeadas
Quando uma consulta tem estágios (total por cliente, depois os primeiros colocados, depois a lista final), dê um nome a cada estágio. Liste várias CTEs após um único WITH, separadas por vírgulas: cada uma pode usar as anteriores, e a consulta é lida de cima para baixo em vez de dentro para fora.
Usar o mesmo resultado mais de uma vez
Uma instrução pode fazer referência a uma CTE quantas vezes quiser, então um resultado que você precisa duas vezes (uma lista de totais e sua média, ou cada total e o total geral) é escrito uma só vez. Com subqueries, você teria que copiar toda a consulta interna.
Percorrer uma hierarquia
Só uma CTE pode fazer referência a si mesma. Uma CTE recursiva começa a partir de algumas linhas e continua fazendo join com a tabela até não aparecerem novas linhas. É assim que você percorre um organograma ou uma árvore de categorias, ou gera uma série de números ou datas.
Exemplos reais de CTEs e subqueries
Produtos com preço acima da média da categoria
A subquery faz referência a p.category da consulta externa, então ela é calculada para a categoria de cada produto (uma subquery 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
O produto mais vendido de cada categoria
Três etapas, cada uma com um nome: somar as unidades por produto, ranquear os produtos dentro de cada categoria e manter o primeiro:
-- 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
A participação de cada região nas vendas
region_totals é usada duas vezes: uma para as linhas e outra dentro de uma subquery para o total geral:
-- 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 com subqueries, a consulta com GROUP BY apareceria duas vezes.
A cadeia de gestores de um funcionário
Comece a partir de um funcionário e siga o manager_id para cima até acabar:
-- 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
Leitura complementar sobre CTEs e subqueries
Neste blog:
- Guia de tabelas temporárias em SQL: mantenha um resultado intermediário por mais de uma instrução
-
Guia para encontrar duplicatas em SQL: CTEs com
ROW_NUMBER()para identificar linhas repetidas - Guia para remover duplicatas em SQL: excluindo duplicatas por meio de uma CTE
- Guia de GROUP BY em SQL: as agregações a partir das quais a maioria das CTEs é construída
- Guia de JOIN em SQL com diagramas: fazendo join de uma CTE com outras tabelas
Documentação oficial:
- PostgreSQL: consultas WITH e expressões de subquery
- MySQL: WITH (common table expressions) e subqueries
- SQLite: a cláusula WITH
- SQL Server: WITH common_table_expression e subqueries
Beekeeper Studio é um cliente de banco de dados gratuito e open source
A melhor ferramenta de consultas e edição de SQL que já usei. Tem tudo o que preciso para gerenciar meu banco de dados. - ⭐⭐⭐⭐⭐ Mit
O Beekeeper Studio é rápido, intuitivo e fácil de usar. Ele suporta vários bancos de dados e funciona muito bem no Windows, Mac e Linux.
O que os usuários dizem sobre o Beekeeper Studio
"O Beekeeper Studio substituiu completamente meu antigo fluxo de trabalho com SQL. É rápido, intuitivo e torna o trabalho com bancos de dados agradável de novo."
"Já experimentei muitas GUIs de banco de dados, mas o Beekeeper encontra o equilíbrio perfeito entre recursos e simplicidade. Simplesmente funciona."