A Consulta SQL
Uma consulta SQL parece uma frase simples, mas na verdade é uma especificação declarativa de um conjunto de resultados que o PostgreSQL tem a liberdade de computar da maneira mais rápida.
Esta página constrói o modelo mental de como o SQL é lido, reordenado e executado pelo PostgreSQL, o que explica por que WHERE e HAVING se comportam de maneira diferente, por que a ordem escrita não é a ordem de execução e por que o mesmo resultado pode vir de planos muito diferentes.
Resumo
- Uma consulta SQL é uma descrição declarativa de um conjunto de resultados, avaliada pelo PostgreSQL em uma ordem lógica fixa que difere da ordem em que você digita as cláusulas.
- Por Que Importa: Conhecer a ordem lógica explica o escopo de aliases, surpresas na comparação de NULL e por que WHERE e HAVING não são intercambiáveis.
- Conceitos Chave: linguagem declarativa, ordem lógica de processamento, predicado sargable, lógica de três valores, planejador baseado em custo.
- Quando Usar: Utilize este modelo sempre que o comportamento de uma consulta o surpreender, ou antes de escolher entre uma subconsulta, join, função de janela ou CTE.
- Limitações / Trade-offs: Entender a ordem lógica explica questões de correção, mas não de desempenho; apenas
EXPLAINinforma o plano físico real. - Tópicos Relacionados: tipos de join, semântica de NULL, subconsultas e EXISTS, operações de conjunto, agregações e GROUP BY.
Fundamentos
SQL é uma linguagem declarativa, o que significa que você descreve a forma do resultado que deseja, em vez dos passos para produzi-lo.
Isso é o oposto de uma linguagem imperativa como um loop em código de aplicação, onde você controla cada passo explicitamente.
Como o SQL é declarativo, o planejador do PostgreSQL tem a liberdade de escolher scans, joins e ordens que não aparecem em nenhum lugar no texto da sua consulta.
As raízes do SQL estão na álgebra relacional, onde uma consulta é uma sequência de operações — seleção, projeção, join e agregação — aplicadas a conjuntos de linhas chamados relações.
Uma tabela é uma relação, uma linha é uma tupla e uma coluna é um atributo, embora o SQL do dia a dia raramente use essa terminologia.
As cláusulas que você escreve, SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY e LIMIT, mapeiam essa álgebra relacional, embora você as digite em uma ordem diferente da que elas executam logicamente.
A ordem lógica de processamento começa com FROM e quaisquer joins, depois WHERE filtra as linhas, depois GROUP BY as agrupa, depois HAVING filtra os grupos, depois SELECT computa as colunas finais, depois ORDER BY ordena e, finalmente, LIMIT corta o resultado.
Essa ordem lógica explica por que você não pode referenciar um alias de SELECT dentro da cláusula WHERE da mesma consulta, mas pode referenciá-lo em ORDER BY, já que WHERE é executado antes de SELECT e ORDER BY é executado depois.
Mecânicas e Interações
O PostgreSQL nunca executa as cláusulas nessa ordem lógica literalmente, pois o planejador tem a liberdade de reordenar as operações, desde que o resultado final corresponda à especificação lógica.
escrito: SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY -> LIMIT
lógico: FROM/JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT
Ver ambos os pedidos lado a lado explica a maior parte do comportamento surpreendente em SQL, desde o escopo de aliases até por que uma cláusula HAVING pode referenciar um agregado que WHERE não pode.
O planejador transforma sua consulta em uma árvore de operadores físicos, decidindo coisas como se deve escanear uma tabela sequencialmente ou através de um índice, e em que ordem juntar múltiplas tabelas.
Essas decisões são impulsionadas por estatísticas que o PostgreSQL mantém sobre o tamanho da tabela e a distribuição dos valores das colunas, que ANALYZE atualiza e o autovacuum aciona automaticamente.
Um predicado é chamado de sargable quando o planejador pode usar um índice para satisfazê-lo diretamente, e envolver uma coluna indexada em uma função geralmente quebra isso, forçando um scan completo em vez disso.
NULL adiciona um terceiro valor lógico, desconhecido, a cada comparação, que é por que WHERE column = NULL nunca corresponde a nada e IS NULL existe como um operador separado.
Operações de conjunto como UNION, INTERSECT e EXCEPT combinam os resultados de duas consultas como conjuntos inteiros, em vez de juntá-las linha por linha.
Subconsultas permitem aninhar o resultado lógico de uma consulta dentro de outra, seja como um valor escalar, uma fonte de linha em FROM, ou um teste booleano com EXISTS.
Subconsultas correlacionadas referenciam as colunas da consulta externa e conceitualmente são reexecutadas por linha externa, embora o planejador frequentemente as reescreva em um plano semelhante a um join internamente.
Considerações Avançadas e Aplicações
O mesmo resultado lógico pode frequentemente ser expresso como uma subconsulta correlacionada, um join, uma função de janela ou uma expressão de tabela comum (CTE), e o planejador do PostgreSQL nem sempre produz o mesmo plano físico para cada forma, mesmo quando as linhas de saída são idênticas.
Expressões de tabela comuns definidas com WITH agiam como uma barreira de otimização antes do PostgreSQL 12, mas a partir do PostgreSQL 12, e ainda válido no 18.4, o planejador inliniza CTEs não recursivas, a menos que você marque uma como MATERIALIZED ou a referencie mais de uma vez.
Funções de janela computam um valor semelhante a um agregado por linha sem colapsar as linhas da maneira que GROUP BY faz, o que as torna a ferramenta certa para totais acumulados, classificações e médias móveis.
EXISTS e IN frequentemente produzem resultados equivalentes para testes de pertencimento, mas EXISTS para no primeiro match e lida com NULLs de forma mais previsível do que NOT IN, que pode silenciosamente retornar nenhuma linha se a subconsulta produzir qualquer NULL.
A legibilidade da consulta e o desempenho da consulta não são o mesmo eixo, portanto, refatorar uma subconsulta aninhada em uma CTE para clareza não deve ser assumido como alterando o plano de execução.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Subconsulta correlacionada | Lê naturalmente de cima para baixo, boa para um único valor derivado | Pode forçar reavaliação por linha se o planejador não conseguir reescrevê-la | Pequenas consultas de lookup, telas administrativas |
| JOIN | Permite que o planejador escolha livremente a ordem e o algoritmo de join | Duplica linhas em relacionamentos de um para muitos, a menos que agregadas | Combinando dados de múltiplas tabelas em escala |
| Função de janela | Mantém o detalhe em nível de linha enquanto adiciona contexto agregado | Sintaxe menos familiar, fácil de usar incorretamente as cláusulas de frame | Classificações, totais acumulados, comparações por grupo |
| Expressão de Tabela Comum (CTE) | Nomeia uma etapa para legibilidade e reutilização dentro de uma consulta | Não garante mais materialização, a menos que solicitado | Dividir uma consulta complexa em etapas legíveis |
A escolha entre essas formas deve começar pelo que a consulta precisa expressar, depois ser verificada com EXPLAIN, não decidida apenas por hábito.
Equívocos Comuns
- SQL executa de cima para baixo como escrito - o planejador tem a liberdade de reordenar operações, desde que o resultado lógico corresponda, então a ordem escrita e a ordem de execução são coisas diferentes.
- SELECT é executado antes de WHERE - logicamente,
WHEREfiltra linhas antes queSELECTcompute as colunas de saída, que é por que um alias deSELECTnão pode ser usado dentro da cláusulaWHEREdessa mesma consulta. - NULL é igual a NULL - NULL representa desconhecido, então
NULL = NULLavalia como desconhecido em vez de verdadeiro, e apenasIS NULLtesta confiavelmente para isso. - Uma subconsulta é sempre mais lenta que um JOIN - o planejador frequentemente reescreve subconsultas em planos equivalentes a joins internamente, então a forma escrita não determina o plano físico.
- DISTINCT e GROUP BY são intercambiáveis - ambos podem deduplicar linhas, mas apenas
GROUP BYé construído para emparelhar com funções de agregação por grupo.
FAQs
Por que minha cláusula WHERE não vê o alias que defini em SELECT?
Porque logicamente WHERE é executado antes que SELECT compute suas colunas de saída, o alias ainda não existe no ponto em que WHERE é avaliado.
Qual é a ordem lógica real em que o SQL é executado?
FROM/joins, depois WHERE, depois GROUP BY, depois HAVING, depois SELECT, depois ORDER BY, depois LIMIT, e a ordem escrita só corresponde a essa sequência para FROM e WHERE.
Por que `WHERE column = NULL` nunca retorna linhas?
NULL significa desconhecido, comparar qualquer coisa com desconhecido resulta em desconhecido em vez de verdadeiro, e IS NULL ou IS NOT NULL é a maneira correta de testar isso em vez disso.
Um JOIN é sempre mais rápido que uma subconsulta?
Não necessariamente, pois o planejador frequentemente reescreve subconsultas correlacionadas em planos equivalentes a joins, então a escolha deve ser baseada primeiro na legibilidade e correção, e depois verificada com EXPLAIN.
Qual é a diferença entre WHERE e HAVING?
WHERE filtra linhas individuais antes que o agrupamento ocorra.
HAVING filtra grupos após GROUP BY ter colapsado as linhas, então ele pode referenciar funções de agregação que WHERE não pode.
CTEs sempre rodam antes do resto da consulta?
Não necessariamente, pois o PostgreSQL 12 inliniza CTEs não recursivas na consulta circundante por padrão, a menos que uma seja marcada como MATERIALIZED ou referenciada mais de uma vez.
Por que a mesma consulta às vezes obtém um plano de execução diferente?
A escolha do planejador depende das estatísticas da tabela, que mudam à medida que os dados crescem ou ANALYZE é executado, então o mesmo texto SQL pode produzir um plano diferente ao longo do tempo.
O que significa "sargable" e por que isso importa?
Um predicado sargable é aquele que o planejador pode satisfazer diretamente com um índice, e envolver uma coluna indexada em uma função ou cast geralmente torna o predicado não sargable, forçando um scan completo em vez disso.
Quando devo usar uma função de janela em vez de GROUP BY?
Use uma função de janela quando precisar de um valor semelhante a um agregado, como um rank ou total acumulado, juntamente com o detalhe original em nível de linha, em vez de colapsar linhas.
Qual é a diferença entre UNION e UNION ALL?
UNION remove linhas duplicadas do resultado combinado, o que requer uma etapa de ordenação ou hash.
UNION ALL mantém todas as linhas e é mais barato quando duplicatas não são uma preocupação.
Por que EXISTS às vezes supera IN?
EXISTS pode parar na primeira linha correspondente e lida com NULLs na subconsulta de forma mais previsível do que NOT IN, que pode retornar silenciosamente nenhuma linha se a subconsulta produzir qualquer NULL.
A formatação do meu SQL afeta o desempenho?
Não, pois a capitalização de palavras-chave, quebras de linha e indentação são puramente para legibilidade humana e não têm efeito na consulta analisada ou no plano escolhido.
Como sei se o PostgreSQL está realmente usando meu índice?
Execute EXPLAIN (ANALYZE, BUFFERS) na consulta e procure por um nó de index scan ou index-only scan em vez de um sequential scan.
Relacionados
- Noções Básicas de Consulta SQL - exemplos práticos de SELECT/WHERE/ORDER BY
- Tipos de Join - como os joins na cláusula FROM combinam relações
- Semântica de Null - lógica de três valores em profundidade
- Subconsultas e EXISTS - padrões de consulta aninhada
- Agregações e GROUP BY - agrupamento e funções de agregação
- UNION, INTERSECT, EXCEPT - operações de conjunto entre consultas
Versões da Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17); o comportamento de inlining de CTE descrito se aplica a partir do PostgreSQL 12.