← Voltar ao blog

SELECT no SQL: a ordem em que o banco executa as cláusulas

FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY: a ordem real de execução explica por que o alias do SELECT falha no WHERE e paginar não acelera nada.


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 SKIP grande obriga o banco a percorrer e descartar tudo o que vem antes.
  • ORDER BY sem í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.

  1. FROM — a junção está trazendo linhas demais? Confira o ON e o tipo de join.
  2. WHERE — o filtro está aplicado na tabela certa? Em LEFT JOIN, condição no WHERE transforma o join em interno.
  3. GROUP BY — as colunas do SELECT estão todas agregadas ou agrupadas?
  4. HAVING — o filtro de grupo está no lugar certo?
  5. Janela — precisa de subconsulta para filtrar o resultado?
  6. ORDER BY — a ordenação usa índice?
  7. 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

  1. Escreva a consulta em blocos, na ordem de execução.
  2. Valide cada bloco isoladamente, com SELECT * e FIRST 10.
  3. Só então adicione agregação e janela.
  4. Verifique o plano com SET PLANONLY ON.
  5. Meça com o volume real, não com a base de teste.
  6. Confira se o ORDER BY usa índice.
  7. Substitua SKIP grande por keyset quando a paginação for profunda.

Checklist

  • Sei que FROM roda antes do SELECT.
  • Não uso alias do SELECT no WHERE.
  • Condição de grupo está no HAVING.
  • Condição de LEFT JOIN está no ON.
  • Sei que o limite de linhas é aplicado por último.
  • Paginação profunda usa keyset, não SKIP alto.
  • 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: