Fundamentos de SQL

CTE em SQL: a cláusula WITH explicada com queries reais

Uma CTE transforma uma query que você precisa ler de dentro para fora em uma query que se lê de cima para baixo. Abaixo: sintaxe, múltiplas CTEs, recursão, a verdade sobre performance e os erros que aparecem em code review.

A sintaxe em 6 linhas

WITH pedidos_recentes AS (
    SELECT order_id, customer_id, amount, ordered_at
    FROM orders
    WHERE ordered_at >= current_date - INTERVAL '30 days'
)
SELECT customer_id, COUNT(*) AS pedidos, SUM(amount) AS receita
FROM pedidos_recentes
GROUP BY customer_id;

pedidos_recentes existe só enquanto esse comando roda. Nada é criado em disco, sem limpeza depois, sem permissão para conceder.

Múltiplas CTEs: um comando, etapas legíveis

É aqui que a CTE se paga — cada etapa tem nome e pode usar a anterior.

WITH pagos AS (
    SELECT * FROM orders WHERE status = 'paid'
),
por_cliente AS (
    SELECT customer_id,
           SUM(amount)      AS receita,
           COUNT(*)         AS pedidos,
           MIN(ordered_at)  AS primeiro_pedido
    FROM pagos
    GROUP BY customer_id
),
classificado AS (
    SELECT *,
           NTILE(4) OVER (ORDER BY receita DESC) AS quartil
    FROM por_cliente
)
SELECT c.name, r.receita, r.pedidos, r.quartil
FROM classificado r
JOIN customers c ON c.id = r.customer_id
WHERE r.quartil = 1
ORDER BY r.receita DESC;

Tente reescrever isso com subqueries aninhadas e você entende por que analytics engineers escrevem quase tudo assim — é exatamente a estrutura de um modelo dbt.

CTE recursiva: percorrendo uma hierarquia

Um organograma com a profundidade de cada pessoa abaixo do CEO:

WITH RECURSIVE org AS (
    -- âncora: o topo da árvore
    SELECT id, name, manager_id, 1 AS nivel
    FROM employees
    WHERE manager_id IS NULL

    UNION ALL

    -- membro recursivo: quem reporta a alguém já encontrado
    SELECT e.id, e.name, e.manager_id, o.nivel + 1
    FROM employees e
    JOIN org o ON e.manager_id = o.id
    WHERE o.nivel < 20          -- sempre limite a recursão
)
SELECT nivel, name FROM org ORDER BY nivel, name;

O limite nivel < 20 não é opcional em produção. Um ciclo nos dados (A gerencia B, B gerencia A) transforma a CTE recursiva numa query que nunca termina.

Mesmo padrão, outro uso — gerar um calendário sem buracos para relatórios:

WITH RECURSIVE datas AS (
    SELECT DATE '2026-01-01' AS d
    UNION ALL
    SELECT d + 1 FROM datas WHERE d < DATE '2026-12-31'
)
SELECT d FROM datas;

CTE vs subquery vs tabela temporária vs view

RecursoVive porUse quando
CTEUm comandoVocê quer etapas nomeadas e legíveis em uma query só
SubqueryUm comandoÉ uma linha só e dar nome não agrega nada
Tabela temporáriaA sessãoO mesmo resultado é reusado por vários comandos, ou você precisa de índice
ViewPara sempreA lógica é compartilhada por várias queries e pessoas

A verdade sobre performance (e a única armadilha)

Engines modernos fazem inline da CTE, então o plano costuma ser igual ao da subquery equivalente. A armadilha é referenciar uma CTE caríssima várias vezes: alguns engines reavaliam a cada uso. Nesse caso, materialize de propósito.

-- PostgreSQL: força uma avaliação e reusa o resultado
WITH pesada AS MATERIALIZED (
    SELECT customer_id, SUM(amount) AS receita
    FROM orders GROUP BY customer_id
)
SELECT * FROM pesada WHERE receita > 1000
UNION ALL
SELECT * FROM pesada WHERE receita < 10;

Confirme sempre com EXPLAIN ANALYZE em vez de adivinhar — esse hábito é o que separa quem é sênior de quem "ouviu falar que CTE é lenta".

Resposta nível entrevista

"CTE é um resultado temporário nomeado com escopo de um comando. Uso para quebrar uma transformação em etapas legíveis e para expressar recursão sobre hierarquias. Normalmente é inlined, então não trato como otimização — se uma CTE pesada é referenciada várias vezes, materializo de propósito e valido no EXPLAIN." Junte isso com funções de janela e você resolve a maioria das rodadas de SQL.

Perguntas frequentes

O que é uma CTE em SQL?
CTE (Common Table Expression) é um resultado temporário com nome, definido com a palavra-chave WITH, que existe apenas durante a execução de um único comando. Você referencia como se fosse uma tabela no SELECT, INSERT, UPDATE ou DELETE seguinte.
Qual a diferença entre CTE e subquery?
Na prática são próximas, mas a CTE tem nome, pode ser referenciada mais de uma vez no mesmo comando e é lida de cima para baixo, em vez de de dentro para fora. Subqueries aninhadas ficam ilegíveis rápido; CTEs deixam você nomear cada etapa da lógica.
CTE é mais rápida que subquery?
Normalmente o plano é idêntico — a maioria dos engines faz inline da CTE. O PostgreSQL 12+ faz inline por padrão, a menos que você escreva MATERIALIZED. Escolha CTE por legibilidade, não por performance.
Posso ter várias CTEs na mesma query?
Sim. Escreva WITH uma vez e separe as definições por vírgula. Uma CTE posterior pode referenciar qualquer CTE anterior — é assim que você monta um pipeline legível de várias etapas em um único comando.
Para que serve uma CTE recursiva?
Hierarquias e grafos: organogramas, árvores de categorias, lista de materiais, calendários (date spine) e caminhos. Ela tem um membro âncora, UNION ALL e um membro recursivo que referencia a própria CTE.
CTE funciona no MySQL, PostgreSQL, Snowflake e BigQuery?
Sim — MySQL 8.0+, PostgreSQL 8.4+, SQL Server 2005+, Snowflake, BigQuery, Databricks e DuckDB suportam WITH, e todos hoje suportam RECURSIVE.

Pronto para assinar?

7 dias grátis. Depois, menos que um café por mês — cancele quando quiser.

Assinar — 7 dias grátis