O banco não lê o SELECT na ordem em que você escreve. Ele começa pelo FROM, filtra com WHERE, agrupa, e só no fim projeta as colunas e ordena. Entender essa ordem explica por que o alias do SELECT falha no WHERE, por que HAVING existe e por que paginar não deixa a consulta mais barata.
Quem escreve SQL pensando na ordem de execução erra menos e depura mais rápido. É o mesmo raciocínio que separa "consulta que funciona" de "consulta que funciona com 10 milhões de linhas".
Qual é a ordem real de execução de um SELECT?
A ordem lógica é: FROM → WHERE → GROUP BY → HAVING → funções de janela → SELECT → DISTINCT → ORDER BY → limite de linhas.
| Ordem escrita | Ordem executada |
|---|---|
SELECT |
6 |
FROM / JOIN |
1 |
WHERE |
2 |
GROUP BY |
3 |
HAVING |
4 |
| Window functions | 5 |
DISTINCT |
7 |
ORDER BY |
8 |
LIMIT / FIRST / OFFSET |
9 |
A ordem escrita foi feita para leitura humana. A ordem executada segue a dependência dos dados: primeiro descobrir as linhas, depois reduzi-las, depois calcular o que será exibido.
Por que o alias do SELECT não funciona no WHERE?
Porque no momento em que o WHERE roda, a lista de colunas do SELECT ainda não foi avaliada. O alias ainda não existe.
-- erro: alias VALOR_TOTAL não existe no WHERE
SELECT QUANTIDADE * VALOR_UNIT AS VALOR_TOTAL
FROM ITENS
WHERE VALOR_TOTAL > 1000;
A correção é repetir a expressão no filtro — ou envolver a consulta em uma subconsulta/CTE, para o alias passar a existir como coluna:
SELECT *
FROM (
SELECT ID, QUANTIDADE * VALOR_UNIT AS VALOR_TOTAL
FROM ITENS
) t
WHERE t.VALOR_TOTAL > 1000;
O ORDER BY é a exceção conhecida: ele roda depois do SELECT, então enxerga o alias.
SELECT QUANTIDADE * VALOR_UNIT AS VALOR_TOTAL
FROM ITENS
ORDER BY VALOR_TOTAL DESC;
Por que HAVING existe se o WHERE já filtra?
Porque WHERE trabalha linha a linha, antes de qualquer agregação. HAVING trabalha sobre o resultado dos grupos. Filtrar COUNT(*) no WHERE é impossível: naquele momento o grupo ainda não foi formado.
SELECT IDCLIENTE, COUNT(*) AS QTD
FROM LANCAMENTOS
WHERE DATA >= '2026-01-01' -- filtra linhas antes de agrupar
GROUP BY IDCLIENTE
HAVING COUNT(*) > 10; -- filtra grupos depois de agrupar
Regra prática: o que depende de linha vai no WHERE; o que depende de grupo vai no HAVING. Colocar condição de linha no HAVING funciona na maioria dos bancos, mas lê pior e pode impedir o uso de índice.
Paginar deixa a consulta mais rápida?
Não. O limite de linhas é aplicado no fim: o banco já leu, filtrou, agrupou e ordenou antes de cortar o resultado.
-- Firebird: FIRST/SKIP
SELECT FIRST 20 SKIP 40 ID, DATA, VALOR
FROM LANCAMENTOS
WHERE IDCLIENTE = 42
ORDER BY DATA DESC;
Isso explica dois comportamentos que confundem:
- Página 1 rápida e página 200 lenta: o
SKIPgrande obriga o banco a percorrer e descartar tudo o que vem antes. ORDER BYsem índice é o custo real: a ordenação acontece antes do corte.
Para paginação profunda, prefira keyset pagination: em vez de SKIP, continue a partir da última chave lida.
SELECT FIRST 20 ID, DATA, VALOR
FROM LANCAMENTOS
WHERE IDCLIENTE = 42
AND DATA < :ultima_data_lida
ORDER BY DATA DESC;
Onde entram as window functions?
Depois do WHERE, do GROUP BY e do HAVING, e antes do ORDER BY. Elas calculam sobre o conjunto já filtrado e agrupado, sem reduzir linhas.
SELECT
ID, IDCLIENTE, DATA, VALOR,
SUM(VALOR) OVER (PARTITION BY IDCLIENTE) AS TOTAL_CLIENTE,
ROW_NUMBER() OVER (PARTITION BY IDCLIENTE ORDER BY DATA DESC) AS ORDEM
FROM LANCAMENTOS
WHERE DATA >= '2026-01-01';
Consequência prática: você não filtra pelo resultado da window function no mesmo nível. Precisa de subconsulta ou CTE.
WITH ranked AS (
SELECT ID, IDCLIENTE, DATA, VALOR,
ROW_NUMBER() OVER (PARTITION BY IDCLIENTE ORDER BY DATA DESC) AS ORDEM
FROM LANCAMENTOS
)
SELECT * FROM ranked WHERE ORDEM = 1;
Como isso ajuda a depurar uma consulta confusa?
Quando o resultado sai errado, a ordem de execução diz onde olhar.
FROM— a junção está trazendo linhas demais? Confira oONe o tipo de join.WHERE— o filtro está aplicado na tabela certa? EmLEFT JOIN, condição noWHEREtransforma o join em interno.GROUP BY— as colunas doSELECTestão todas agregadas ou agrupadas?HAVING— o filtro de grupo está no lugar certo?- Janela — precisa de subconsulta para filtrar o resultado?
ORDER BY— a ordenação usa índice?- Limite — a paginação é a causa da lentidão?
O item 2 é campeão de bugs silenciosos:
-- errado: o WHERE anula o LEFT JOIN
SELECT c.ID, c.NOME, l.VALOR
FROM CLIENTES c
LEFT JOIN LANCAMENTOS l ON l.IDCLIENTE = c.ID
WHERE l.STATUS = 'A';
A condição deve ir no ON para preservar clientes sem lançamento:
SELECT c.ID, c.NOME, l.VALOR
FROM CLIENTES c
LEFT JOIN LANCAMENTOS l
ON l.IDCLIENTE = c.ID
AND l.STATUS = 'A';
Passo a passo para reescrever uma consulta confusa
- Escreva a consulta em blocos, na ordem de execução.
- Valide cada bloco isoladamente, com
SELECT *eFIRST 10. - Só então adicione agregação e janela.
- Verifique o plano com
SET PLANONLY ON. - Meça com o volume real, não com a base de teste.
- Confira se o
ORDER BYusa índice. - Substitua
SKIPgrande por keyset quando a paginação for profunda.
Checklist
- Sei que
FROMroda antes doSELECT. - Não uso alias do
SELECTnoWHERE. - Condição de grupo está no
HAVING. - Condição de
LEFT JOINestá noON. - Sei que o limite de linhas é aplicado por último.
- Paginação profunda usa keyset, não
SKIPalto. - Window function filtrada em subconsulta.
Perguntas frequentes
O banco executa o SELECT na ordem em que escrevo?
Não. A ordem escrita serve para leitura. A execução começa pelo FROM, passa por WHERE, GROUP BY e HAVING, e só no fim projeta colunas, remove duplicatas, ordena e corta o resultado.
Por que não posso usar o alias da coluna no WHERE?
Porque o alias é criado na etapa do SELECT, que roda depois do WHERE. Para filtrar pelo valor calculado, repita a expressão ou envolva a consulta em subconsulta ou CTE.
Por que a última página da listagem demora mais?
Porque SKIP/OFFSET descarta linha por linha até chegar ao ponto pedido. Quanto maior o deslocamento, mais trabalho. Keyset pagination resolve, filtrando pela última chave lida.
Conclusão
Escrever SQL pensando na ordem de execução resolve três classes de problema de uma vez: alias que não existe, filtro no lugar errado e paginação cara. O banco segue sempre a mesma sequência — quem se adapta a ela para de adivinhar.
Para montar a consulta com a estrutura certa desde o começo, o Sqlify transforma a descrição em português no SQL pronto, com WHERE, GROUP BY e junções no lugar — e explica cada cláusula. Descreva o relatório que precisa e revise a query gerada.
Leia também: