SQL Avançado Desmistificado
SQL avançado não é uma linguagem diferente do SQL que você já escreve, é o mesmo modelo declarativo estendido para cobrir problemas que antes forçavam uma ida de volta ao código da aplicação.
Busque em todas as páginas da documentação
SQL avançado não é uma linguagem diferente do SQL que você já escreve, é o mesmo modelo declarativo estendido para cobrir problemas que antes forçavam uma ida de volta ao código da aplicação.
CTEs, funções de janela, joins LATERAL e consultas recursivas existem para permitir que uma única instrução descreva lógica que, de outra forma, exigiria loops, tabelas temporárias ou várias idas e vindas ao banco de dados.
Esta página constrói o modelo mental por trás dessas quatro ferramentas para que as páginas dedicadas a CTEs, funções de janela, joins LATERAL e CTEs recursivas sejam lidas como aplicações de uma única ideia, em vez de quatro sintaxes não relacionadas para memorizar.
Todo recurso avançado de SQL nesta página ainda é apenas uma consulta sobre conjuntos de linhas, a diferença está apenas em quanta estrutura você pode impor a essa computação antes que o resultado final retorne.
Uma CTE (a cláusula WITH) não é nada mais do que uma subconsulta nomeada que você pode referenciar posteriormente na mesma instrução, existindo puramente para tornar uma consulta longa legível, dividindo-a em etapas nomeadas.
Uma função de janela computa um valor em um conjunto de linhas relacionadas, chamado sua partição, sem mesclar essas linhas em uma única linha de saída como o GROUP BY faria.
LATERAL permite que uma subconsulta na cláusula FROM veja colunas de itens FROM que aparecem antes dela, executando efetivamente uma pequena consulta correlacionada uma vez por linha externa, ainda produzindo um único resultado baseado em conjuntos.
Uma CTE recursiva repete uma consulta contra seu próprio resultado crescente até que nenhuma nova linha apareça, que é como o SQL expressa caminhadas de hierarquia e travessia de grafos sem um loop do lado do cliente.
A maneira mais simples de manter as quatro em mente é pensar em uma instrução SQL como uma linha de montagem: CTEs nomeiam as estações, funções de janela adicionam colunas computadas sem quebrar a linha em cintos separados, LATERAL permite que uma estação olhe para trás para a linha de saída de uma estação anterior, linha por linha, e a recursão permite que uma estação realimente sua própria saída até que a linha pare naturalmente.
O planejador de consultas processa as cláusulas de uma instrução em uma ordem lógica fixa que é diferente da ordem em que você as digita, e entender essa ordem explica por que as funções de janela podem referenciar resultados de GROUP BY, mas não o contrário.
FROM / JOIN -> WHERE -> GROUP BY -> HAVING
-> funções de janela -> lista SELECT
-> DISTINCT -> ORDER BY -> LIMIT / OFFSETComo as funções de janela são executadas após o agrupamento e a filtragem, mas antes que a lista SELECT final seja montada, elas podem classificar ou comparar linhas já agregadas sem a necessidade de uma segunda passagem pela tabela.
Uma CTE não recursiva não é automaticamente uma "barreira de otimização" como era em versões mais antigas do PostgreSQL; desde o PostgreSQL 12, o planejador pode incorporar uma consulta WITH na instrução circundante exatamente como uma subconsulta, a menos que seja referenciada mais de uma vez ou marcada como MATERIALIZED.
Essa distinção é importante porque uma CTE incorporada permite que o planejador empurre filtros para dentro dela, enquanto uma materializada é computada uma vez e depois lida novamente, o que pode ajudar quando a mesma CTE é reutilizada várias vezes, mas pode prejudicar quando impede o empurrão de filtros.
A correlação LATERAL é mecanicamente semelhante a uma subconsulta por linha, mas como ela reside na cláusula FROM, ela pode retornar várias colunas e várias linhas por linha externa, o que é exatamente o que uma subconsulta correlacionada simples na lista SELECT não pode fazer.
SELECT a.id, recent.total
FROM app.accounts a
JOIN LATERAL (
SELECT total FROM app.orders o
WHERE o.account_id = a.id
ORDER BY o.created_at DESC LIMIT 1
) recent ON true;CTEs recursivas são executadas como um loop de tabela de trabalho: o termo não recursivo semeia o primeiro lote de linhas, o termo recursivo é executado novamente contra apenas as linhas produzidas na iteração anterior, e o loop para no instante em que uma iteração produz zero novas linhas.
Esse loop não tem detecção de ciclo embutida, portanto, uma hierarquia autorreferenciada com um ciclo girará até que você adicione uma guarda explícita, geralmente um array de chaves visitadas verificadas com NOT (id = ANY(path)).
Funções de janela não têm um tipo de índice dedicado próprio, mas um índice que corresponde às colunas PARTITION BY e ORDER BY permite que o planejador evite uma etapa de ordenação explícita antes de computar a janela, o que geralmente é o maior custo em uma consulta de janela.
CTEs recursivas têm um custo real de materialização por iteração, pois o PostgreSQL precisa armazenar a tabela de trabalho entre as etapas, portanto, uma caminhada de grafo em uma tabela grande e mal delimitada pode consumir muito mais work_mem e tempo do que o loop equivalente em código de aplicação.
O pivoteamento de linhas em colunas pode ser feito com agregação condicional usando FILTER, com crosstab() da extensão tablefunc, ou com jsonb_object_agg() quando as colunas de destino não são conhecidas antecipadamente, e cada uma dessas opções troca a simplicidade de formato fixo por flexibilidade dinâmica de maneiras diferentes.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
CTE (WITH) | Nomes de etapas intermediárias para legibilidade e reutilização | A materialização pode bloquear o empurrão de filtros se reutilizada ou forçada | Dividir uma consulta longa em estágios nomeados e revisáveis |
| Função de janela | Computa classificações por linha ou valores correntes sem colapsar linhas | Sem tipo de índice dedicado, depende de índices amigáveis à ordenação | Top-N por grupo, totais correntes, comparações linha a linha |
| Join LATERAL | Subconsulta correlacionada que pode retornar múltiplas linhas e colunas por linha externa | Precisa de um índice de suporte na coluna correlacionada ou degrada para loops aninhados em tudo | Consultas "últimas N" por linha que um join simples não pode expressar |
| CTE Recursiva | Expressa travessia de hierarquia e grafo de forma declarativa | Materializa uma tabela de trabalho por iteração, precisa de guardas de ciclo explícitas | Organogramas, listas de materiais, grafos de dependência |
Pivô FILTER | Nenhuma extensão necessária, amigável ao planejador em uma única varredura | O conjunto de colunas deve ser conhecido no momento da consulta | Pequenos conjuntos fixos de colunas de pivô |
Muitas equipes recorrem a loops do lado da aplicação por hábito, mesmo depois que uma consulta poderia expressar a mesma lógica declarativamente, e o compromisso honesto é que as versões SQL desses padrões são menos familiares para ler rapidamente, mas evitam a latência de enviar linhas de volta e para frente entre o banco de dados e a camada de aplicação.
WITH simples hoje é incorporada como uma subconsulta, a menos que seja referenciada várias vezes ou explicitamente marcada como MATERIALIZED.GROUP BY para funcionar." Uma função de janela pode ser executada em todo o conjunto de resultados sem nenhum GROUP BY, pois PARTITION BY dentro da cláusula OVER define seu próprio agrupamento independente do GROUP BY da consulta.SELECT só pode retornar um valor escalar por linha externa, enquanto LATERAL na cláusula FROM pode retornar várias linhas e colunas por linha externa.tablefunc." A agregação condicional com FILTER lida com colunas de pivô fixas e conhecidas sem instalar nada extra.Uma CTE é uma subconsulta nomeada declarada em uma cláusula WITH, e desde o PostgreSQL 12, o planejador trata uma CTE referenciada uma única vez, não recursiva, exatamente como uma subconsulta inline, a menos que você force a materialização.
INSERT/UPDATE/DELETE com efeito colateral que deve ser executado exatamente uma vezFunções de janela são executadas após GROUP BY e HAVING, mas antes que a lista SELECT final seja montada, então elas podem operar em linhas já agrupadas.
Não no mesmo SELECT, pois as funções de janela são computadas após WHERE, então você precisa envolver a consulta em uma CTE ou subconsulta e filtrar a consulta externa em vez disso.
Geralmente é a ordenação por trás de PARTITION BY/ORDER BY, e não a computação da janela em si, e um índice que corresponde a essas colunas muitas vezes remove essa ordenação completamente.
Uma condição de JOIN normal não pode referenciar colunas computadas dentro da subconsulta unida, enquanto LATERAL permite que a subconsulta à direita veja colunas de itens FROM à sua esquerda, avaliada uma vez por linha externa.
Obter o único pedido mais recente de cada conta, ou seus três principais pedidos, em uma única consulta em vez de executar uma consulta separada para cada conta a partir do código da aplicação.
NOT (id = ANY(path)) antes de recursar maisNão, é um loop iterativo de ponto fixo sobre uma tabela de trabalho, avaliado pelo executor etapa por etapa em vez de através de chamadas de função aninhadas.
Apenas quando as colunas de pivô não são conhecidas antecipadamente ou você deseja a conveniência de crosstab(), pois a agregação condicional com FILTER lida com conjuntos de colunas fixos sem nenhuma extensão.
Sim, e é um padrão comum computar uma função de janela dentro de uma CTE e, em seguida, filtrar ou unir contra seu resultado na consulta externa, pois as funções de janela não podem ser filtradas no mesmo SELECT em que aparecem.
Assumir que nomear algo em uma cláusula WITH o torna automaticamente mais rápido, quando na realidade o comportamento de incorporação do planejador significa que o desempenho de uma CTE é geralmente idêntico a escrever a mesma lógica como uma subconsulta simples.
Prefira-os quando a lógica for naturalmente baseada em conjuntos e, de outra forma, custaria várias idas e vindas, mas mantenha a lógica de negócios genuinamente procedural e ramificada no código da aplicação, onde é mais fácil testar e raciocinar sobre ela.
OVER, PARTITION BY e frameFROMVersões do Stack: Esta página foi escrita para PostgreSQL 18.4 (linha estável 18, linha de manutenção 17), onde o comportamento de incorporação de CTE introduzido no PostgreSQL 12 e os mecanismos de função de janela e consulta recursiva descritos aqui permanecem atuais.
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026