SQL para análise de dados · Guias · 30 de outubro de 2026 · 13 min de leitura

ORDER BY no SQL: ASC, DESC, várias colunas, NULLS LAST e top N

Aviões enfileirados numa pista do menor para o maior, com marcadores de posição numerados

O ORDER BY ordena as linhas do resultado de uma consulta. Ele vai no final da consulta: você lista as colunas pelas quais quer ordenar e acrescenta ASC (crescente, o padrão) ou DESC (decrescente) depois de cada uma. ORDER BY range DESC coloca primeiro o avião de maior alcance. Com várias colunas, a segunda só desempata a primeira. Sem ORDER BY, o banco devolve as linhas sem nenhuma ordem garantida, então toda consulta em que a ordem importa, e toda consulta com LIMIT, precisa dele. Dois detalhes são os que mais pegam as pessoas: onde ficam os NULLs (muda de banco para banco, então escreva NULLS FIRST ou NULLS LAST) e os empates no corte de um top N.

Os exemplos rodam no banco de dados de uma companhia aérea usado no curso de SQL da Nomto, com sintaxe do PostgreSQL executada no DuckDB (o motor da sala de SQL da Nomto). Os textos dos dados estão em inglês, e cada tabela de resultado saiu da execução real da consulta.

Sintaxe do ORDER BY

SELECT colunas
FROM tabela
WHERE condição
ORDER BY coluna1 [ASC | DESC] [NULLS FIRST | NULLS LAST],
         coluna2 [ASC | DESC] ...
LIMIT n;

O ORDER BY vem depois de WHERE, GROUP BY e HAVING, e antes de LIMIT. Ele também é um dos últimos passos que o banco executa, e é por isso que pode usar os aliases do SELECT (mais sobre isso abaixo).

Ordem crescente e decrescente

Os nove modelos de avião, ordenados por alcance, do menor para o maior:

SELECT model, range
FROM aircrafts
ORDER BY range;
model range
Cessna 208 Caravan 1200
Bombardier CRJ-200 2700
Sukhoi Superjet-100 3000
Boeing 737-300 4200
Airbus A321-200 5600
Airbus A320-200 5700
Airbus A319-100 6700
Boeing 767-300 7900
Boeing 777-300 11100

ORDER BY range é o mesmo que ORDER BY range ASC. Para o maior primeiro, escreva ORDER BY range DESC. Números são ordenados pelo valor, datas e timestamps da mais antiga para a mais recente, e textos em ordem alfabética (com as ressalvas da seção sobre ordenar texto).

ORDER BY com várias colunas

Liste várias colunas separadas por vírgula. As linhas são ordenadas pela primeira; a segunda só entra quando a primeira é igual, e assim por diante. Cada coluna tem a sua própria direção. Aviões agrupados por fabricante de A a Z e, dentro de cada fabricante, por alcance do maior para o menor:

SELECT split_part(model, ' ', 1) AS maker, model, range
FROM aircrafts
ORDER BY maker, range DESC;
maker model range
Airbus Airbus A319-100 6700
Airbus Airbus A320-200 5700
Airbus Airbus A321-200 5600
Boeing Boeing 777-300 11100
Boeing Boeing 767-300 7900
Boeing Boeing 737-300 4200
Bombardier Bombardier CRJ-200 2700
Cessna Cessna 208 Caravan 1200
Sukhoi Sukhoi Superjet-100 3000

Um erro frequente é escrever ORDER BY maker, range DESC e esperar que as duas colunas fiquem em ordem decrescente. O DESC vale só para a coluna logo antes dele; aqui maker continua crescente.

Ordenar por alias, posição da coluna ou expressão

O ORDER BY pode se referir a:

Ordenando um resultado agrupado pela agregação, por alias ou por posição:

SELECT status, COUNT(*) AS flights
FROM flights
GROUP BY status
ORDER BY flights DESC;   -- ou ORDER BY 2 DESC, ou ORDER BY COUNT(*) DESC
status flights
Arrived 16707
Scheduled 15383
On Time 518
Cancelled 414
Departed 58
Delayed 41

As posições (ORDER BY 2) são práticas enquanto você explora os dados, mas frágeis em consultas salvas: acrescente uma coluna no início do SELECT e a ordenação muda sem aviso. Prefira nomes em tudo o que outra pessoa vai ler.

Duas restrições que vale conhecer. Um alias pode ser usado sozinho, mas nem sempre dentro de uma expressão: o PostgreSQL recusa ORDER BY flights * 2 quando flights é um alias. E com SELECT DISTINCT, o PostgreSQL exige que toda expressão do ORDER BY apareça na lista do select (“for SELECT DISTINCT, ORDER BY expressions must appear in select list”); o DuckDB é mais tolerante, mas não conte com isso.

Ordem personalizada com CASE

A ordem alfabética dos status não diz nada; o que as pessoas querem ver é a ordem do ciclo de vida do voo. Dê um número a cada valor com CASE e ordene por ele:

SELECT status, COUNT(*) AS flights
FROM flights
GROUP BY status
ORDER BY CASE status
           WHEN 'Scheduled' THEN 1
           WHEN 'On Time'   THEN 2
           WHEN 'Delayed'   THEN 3
           WHEN 'Departed'  THEN 4
           WHEN 'Arrived'   THEN 5
           ELSE 6
         END;
status flights
Scheduled 15383
On Time 518
Delayed 41
Departed 58
Arrived 16707
Cancelled 414

O ELSE manda qualquer valor inesperado para o fim, e não para o início. O mesmo truque fixa um valor no topo: ORDER BY city = 'Moscow' DESC, city lista Moscou primeiro e o resto em ordem alfabética (no PostgreSQL e no DuckDB, true vem depois de false, daí o DESC). O MySQL também tem ORDER BY FIELD(status, 'Scheduled', 'On Time', ...), que não existe em outros bancos. Mais padrões de CASE estão no guia de CASE WHEN no SQL.

NULLS FIRST e NULLS LAST

Quais voos partiram com o maior atraso? 16.348 voos ainda não partiram, então o actual_departure deles, e o atraso calculado, é NULL:

SELECT flight_no, departure_airport, scheduled_departure,
       EXTRACT(EPOCH FROM actual_departure - scheduled_departure) / 60 AS delay_min
FROM flights
ORDER BY delay_min DESC NULLS LAST
LIMIT 5;
flight_no departure_airport scheduled_departure delay_min
PG0589 PEE 2026-07-25 15:30:00 277
PG0164 DME 2026-07-25 15:25:00 268
PG0364 KRR 2026-07-15 11:45:00 267
PG0568 OVS 2026-08-09 16:15:00 260
PG0096 URJ 2026-07-30 16:35:00 258

Troque por NULLS FIRST e o topo da lista vira voos sem nenhum valor de atraso:

flight_no departure_airport scheduled_departure delay_min
PG0402 DME 2026-09-05 12:25:00 NULL
PG0405 DME 2026-08-12 09:35:00 NULL
PG0404 DME 2026-08-12 19:05:00 NULL

O que acontece quando você não escreve nenhum dos dois depende do banco:

banco NULLs no ASC NULLs no DESC
PostgreSQL, Oracle no fim no início
DuckDB no fim no fim
MySQL, SQL Server, SQLite no início no fim

Então o mesmo ORDER BY delay_min DESC LIMIT 5 devolve o top 5 de verdade no DuckDB, mas cinco NULLs no PostgreSQL. PostgreSQL, Oracle, DuckDB e SQLite (a partir da 3.30) aceitam NULLS FIRST/NULLS LAST. MySQL e SQL Server não; neles, ordene primeiro por uma marcação: ORDER BY delay_min IS NULL, delay_min DESC no MySQL, ou ORDER BY CASE WHEN delay_min IS NULL THEN 1 ELSE 0 END, delay_min DESC no SQL Server. O guia de NULL no SQL conta mais sobre como o NULL se comporta.

ORDER BY com LIMIT: as N primeiras linhas

ORDER BY mais LIMIT é o jeito de obter “os N maiores”. Os quatro destinos mais frequentes a partir de Sheremetyevo (SVO):

SELECT arrival_airport, COUNT(*) AS flights
FROM flights
WHERE departure_airport = 'SVO'
GROUP BY arrival_airport
ORDER BY flights DESC, arrival_airport
LIMIT 4;
arrival_airport flights
LED 305
BZK 183
GOJ 122
PES 122

Empates no corte

Três aeroportos, GOJ, PES e ULV, têm 122 voos cada, e só dois cabem no top 4. Sem a segunda coluna de ordenação, arrival_airport, o banco fica livre para escolher quaisquer dois deles, e escolhe: cinco execuções da consulta só com ORDER BY flights DESC deram cinco pares diferentes em 3º e 4º lugar (PES e ULV, GOJ e PES, ULV e PES, GOJ e ULV, PES e GOJ). Um relatório que muda a cada atualização é um bug. Duas soluções:

  1. Acrescentar um critério de desempate que torne a ordem única, como acima. O resultado fica estável, mas o corte continua arbitrário: ULV fica de fora só por causa do nome.
  2. Manter todas as linhas empatadas. PostgreSQL 13+ e Oracle aceitam FETCH FIRST 4 ROWS WITH TIES, e o SQL Server tem TOP 4 WITH TIES. DuckDB e MySQL não têm nenhum dos dois; use RANK():
SELECT arrival_airport, flights
FROM (
  SELECT arrival_airport, COUNT(*) AS flights,
         RANK() OVER (ORDER BY COUNT(*) DESC) AS rnk
  FROM flights
  WHERE departure_airport = 'SVO'
  GROUP BY arrival_airport
) t
WHERE rnk <= 4
ORDER BY flights DESC, arrival_airport;
arrival_airport flights
LED 305
BZK 183
GOJ 122
PES 122
ULV 122

Cinco linhas, porque três aeroportos dividem o 3º lugar. Para o top N dentro de cada grupo (as 3 rotas principais de cada aeroporto, por exemplo), veja o guia de funções de janela e a comparação ROW_NUMBER, RANK e DENSE_RANK.

LIMIT em outros bancos

banco top 3
PostgreSQL, DuckDB, MySQL, SQLite ORDER BY range DESC LIMIT 3
SQL Server SELECT TOP 3 ... ORDER BY range DESC
SQL padrão (PostgreSQL, Oracle 12c+, SQL Server, DuckDB) ORDER BY range DESC FETCH FIRST 3 ROWS ONLY

Paginação com OFFSET

O OFFSET pula linhas antes de o LIMIT começar a contar. A página 2 da lista de aviões, três por página:

SELECT model, range
FROM aircrafts
ORDER BY range DESC
LIMIT 3 OFFSET 3;
model range
Airbus A320-200 5700
Airbus A321-200 5600
Boeing 737-300 4200

A forma padrão OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY devolve as mesmas linhas e também funciona no SQL Server, onde o OFFSET exige um ORDER BY. A paginação só faz sentido com uma ordem única: com empates, uma linha pode aparecer em duas páginas ou em nenhuma. Em tabelas grandes, offsets profundos são lentos, porque o banco ainda precisa produzir e pular todas as linhas anteriores; as aplicações costumam passar para a paginação por “keyset” (WHERE range < ultimo_valor_visto ORDER BY range DESC LIMIT 3).

Ordenar texto: números guardados como texto, maiúsculas e acentos

Texto é comparado caractere por caractere, então números guardados como texto ficam na ordem “errada”. Os números de assento do Airbus A319 são textos como 1A ou 10C:

SELECT seat_no FROM seats WHERE aircraft_code = '319' ORDER BY seat_no LIMIT 8;
seat_no
10A
10B
10C
10D
10E
10F
11A
11B

A fileira 10 vem antes da fileira 1 porque “1” seguido de “0” vem antes de “1” seguido de “A”. Ordene pela parte numérica e depois pela letra:

SELECT seat_no, fare_conditions
FROM seats
WHERE aircraft_code = '319'
ORDER BY CAST(regexp_extract(seat_no, '^[0-9]+') AS INTEGER), right(seat_no, 1)
LIMIT 8;
seat_no fare_conditions
1A Business
1C Business
1D Business
1F Business
2A Business
2C Business
2D Business
2F Business

No PostgreSQL, troque regexp_extract(seat_no, '^[0-9]+') por substring(seat_no FROM '^[0-9]+'). O mesmo problema aparece com IDs e valores carregados como texto, e com CPF, CEP ou códigos de produto que alguém importou de uma planilha: '10' < '9'. A solução de verdade é guardar números como números.

As maiúsculas são a outra surpresa. O DuckDB compara texto pelos códigos dos caracteres, então todas as maiúsculas vêm antes de todas as minúsculas:

SELECT x FROM (VALUES ('banana'), ('Cherry'), ('apple'), ('Apple')) t(x)
ORDER BY x;
x
Apple
Cherry
apple
banana

O PostgreSQL com uma collation típica (en_US ou pt_BR) devolve apple, Apple, banana, Cherry; MySQL e SQL Server, com as collations padrão que não diferenciam maiúsculas, tratam apple e Apple como iguais. Para ter a mesma ordem em todo lugar, ordene por lower(x), com x como desempate:

x
Apple
apple
banana
Cherry

Em português ainda tem os acentos. Pelo código dos caracteres, as letras acentuadas vão para depois do “z”:

SELECT x FROM (VALUES ('Óbidos'), ('Belém'), ('Ávila'), ('Natal'), ('Aracaju')) t(x)
ORDER BY x;
x
Aracaju
Belém
Natal
Ávila
Óbidos

Uma collation de português resolve: no DuckDB, ORDER BY x COLLATE pt devolve Aracaju, Ávila, Belém, Natal, Óbidos. No PostgreSQL, isso depende da collation do banco (pt_BR.UTF-8 ou a ICU pt-BR-x-icu, que também dá para pôr na consulta com COLLATE); no SQL Server, de uma collation como Latin1_General_CI_AI; no MySQL 8, a padrão utf8mb4_0900_ai_ci já ordena Ávila junto com o A. Quando não há collation à mão, ORDER BY strip_accents(lower(x)) (no PostgreSQL, unaccent(lower(x))) chega ao mesmo resultado.

Ordem aleatória

Para pegar linhas aleatórias, ordene por um número aleatório:

SELECT member_id, signup_channel
FROM loyalty_members
ORDER BY random()
LIMIT 3;
member_id signup_channel
3742 app
26779 airport
36510 app

As suas três linhas vão ser outras; a ideia é justamente essa. A função é random() no PostgreSQL, no DuckDB e no SQLite, RAND() no MySQL, e o SQL Server usa ORDER BY NEWID(). Ordenar uma tabela grande inteira para ficar com três linhas é caro; para uma amostra de uma tabela grande, PostgreSQL e DuckDB oferecem TABLESAMPLE (e o DuckDB também USING SAMPLE).

Erros comuns

Essas regras aparecem de novo no guia de comandos SQL básicos e na cola de SQL.

Perguntas frequentes

Qual é a ordem padrão do ORDER BY no SQL?

Crescente (ASC): menores números, datas mais antigas e o início do alfabeto primeiro. O DESC inverte. Onde os NULLs ficam por padrão depende do banco, então deixe explícito com NULLS FIRST ou NULLS LAST.

Como fazer ORDER BY com várias colunas?

Liste as colunas separadas por vírgula, cada uma com a sua direção: ORDER BY maker ASC, range DESC. A segunda coluna só decide a ordem entre as linhas que são iguais na primeira.

Posso usar um alias de coluna no ORDER BY?

Sim. O ORDER BY roda depois do SELECT, então SELECT COUNT(*) AS flights ... ORDER BY flights DESC funciona em todos os bancos principais. Você não pode usar o alias no WHERE, que roda antes do SELECT.

Como colocar os valores NULL por último?

Acrescente NULLS LAST depois da coluna: ORDER BY delay_min DESC NULLS LAST. No MySQL e no SQL Server, que não têm essa sintaxe, ordene primeiro por uma marcação de “é nulo”, por exemplo ORDER BY delay_min IS NULL, delay_min DESC no MySQL.

Posso fazer ORDER BY por uma coluna que não está no SELECT?

Sim, exceto com SELECT DISTINCT (no PostgreSQL e no SQL Server) ou quando a coluna não está disponível depois do GROUP BY. Numa consulta agrupada, você pode ordenar por uma coluna agrupada ou por uma agregação como COUNT(*).

O ORDER BY deixa a consulta mais lenta?

Ordenar custa tempo em resultados grandes, principalmente sem LIMIT. Com ORDER BY ... LIMIT n, os bancos usam uma ordenação top-N que guarda só n linhas na memória, e um índice na coluna de ordenação pode evitar a ordenação no PostgreSQL, no MySQL ou no SQL Server.

Outros idiomas: English, Español, Tiếng Việt, 简体中文