Como paginar resultados no SQL Server com OFFSET FETCH
Quando uma consulta no SQL Server devolve milhares de linhas, mostrar tudo de uma vez é impraticável: o melhor é dividir os dados em páginas. A forma mais simples e legível de paginar resultados no SQL Server é a cláusula OFFSET FETCH, disponível desde o SQL Server 2012 e também no Azure SQL Database. A seguir tens um exemplo prático, passo a passo, e o erro mais comum a evitar. O OFFSET indica quantas linhas saltar e o FETCH quantas devolver a seguir.
Pré-requisitos
- Uma instância de SQL Server 2012 ou superior (também funciona no Azure SQL Database).
- O SQL Server Management Studio (SSMS) ou o Azure Data Studio para correr as consultas.
- Uma tabela com vários registos para testar — no exemplo usamos
dbo.Encomendas. - Noções básicas de
SELECTeORDER BY.
Passo 1: Perceber porque o ORDER BY é obrigatório
A cláusula OFFSET FETCH funciona em dois tempos: primeiro "salta" um número de linhas e depois "traz" as linhas seguintes. Para que saltar e trazer tenham significado, o SQL Server precisa de uma ordem bem definida — por isso o OFFSET só pode aparecer a seguir a um ORDER BY. Sem ordenação, o motor não garante qual a linha que vem primeiro e a paginação ficaria imprevisível.
Erro comum: escreverOFFSETsemORDER BY. Nesse caso o SQL Server devolve um erro de sintaxe, porque oOFFSETfaz parte da própria cláusulaORDER BY. Ordena sempre antes de saltar linhas.
Passo 2: Conhecer a sintaxe OFFSET FETCH
A estrutura é sempre a mesma: ordenas, saltas com OFFSET e trazes um bloco com FETCH NEXT. Este exemplo devolve as 10 encomendas mais recentes:
SELECT EncomendaID, Cliente, DataEncomenda, Total
FROM dbo.Encomendas
ORDER BY DataEncomenda DESC, EncomendaID DESC
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY;
OFFSET 0 ROWS quer dizer "não saltes nenhuma linha" e FETCH NEXT 10 ROWS ONLY quer dizer "traz apenas as 10 seguintes". As palavras ROWS/ROW e NEXT/FIRST são sinónimos — escolhe as que tornarem a consulta mais legível.
Passo 3: Calcular a página com uma fórmula simples
Para saltar para qualquer página só precisas de dois valores: o número da página e o tamanho da página. O número de linhas a saltar é (página - 1) * tamanho. Este exemplo mostra a página 3, com 10 registos por página, ou seja, salta as primeiras 20 linhas:
DECLARE @Pagina INT = 3;
DECLARE @Tamanho INT = 10;
SELECT EncomendaID, Cliente, DataEncomenda, Total
FROM dbo.Encomendas
ORDER BY DataEncomenda DESC, EncomendaID DESC
OFFSET (@Pagina - 1) * @Tamanho ROWS
FETCH NEXT @Tamanho ROWS ONLY;
Mudando apenas o valor de @Pagina para 1, 2, 3… percorres todos os resultados, bloco a bloco. É exatamente este o padrão que corre por trás de uma tabela com botões "Anterior" e "Seguinte" ou de uma API que devolve dados paginados. Mantém o @Tamanho igual em todas as páginas para que a navegação seja consistente.
Passo 4: Ordenar por uma coluna única para paginação estável
Se ordenares apenas por uma coluna com valores repetidos (por exemplo, só por DataEncomenda), duas encomendas com a mesma data podem trocar de posição entre execuções e acabar na página errada — ou aparecer duas vezes. A solução é acrescentar sempre uma coluna única como critério de desempate, normalmente a chave primária:
ORDER BY DataEncomenda DESC, EncomendaID DESC
Com um desempate único, a ordem passa a ser determinística e cada linha aparece exatamente numa página.
Verificar o resultado
Corre a consulta com @Pagina = 1 e depois com @Pagina = 2: cada uma deve devolver 10 linhas e não pode existir nenhum registo repetido entre as duas páginas. Para saber quantas páginas existem no total, divide o número de linhas pelo tamanho da página e arredonda para cima:
DECLARE @Tamanho INT = 10;
SELECT CEILING(COUNT(*) * 1.0 / @Tamanho) AS TotalPaginas
FROM dbo.Encomendas;
Se a última página trouxer menos de 10 linhas, está tudo certo: é apenas o resto dos registos.
Conclusão
Com ORDER BY mais OFFSET FETCH tens uma paginação legível, padronizada e fácil de manter, ideal para alimentar tabelas na interface e APIs. Para páginas muito profundas — quando saltas dezenas de milhares de linhas — o OFFSET pode tornar-se lento; nesse cenário vale a pena estudar a keyset pagination, que pagina a partir da última chave lida. Esta abordagem faz parte do padrão ANSI SQL, por isso o mesmo raciocínio aplica-se a outros motores como o PostgreSQL. Um bom próximo passo é criar um índice pelas colunas do teu ORDER BY e comparar o tempo de resposta. Qual das tuas consultas mais pesadas ias paginar primeiro?