Selecionando os registros 21º ao 30º de uma tabela (paginação no SQL Server)
Com TOP, selecionar os 20 primeiros registros de uma tabela é simples. A dúvida comum é: e se eu quiser os registros do 21º ao 30º — por exemplo, para montar uma página 3 de resultados, com 10 registros por página?
A ideia original: TOP + subquery
Uma forma de resolver é combinar TOP com uma subquery, selecionando os registros que não estão entre os primeiros 20:
SELECT TOP 10 codigo
FROM tbwebmundi
WHERE codigo NOT IN (SELECT TOP 20 codigo FROM tbwebmundi ORDER BY codigo)
ORDER BY codigo;
Ou seja: pegamos os primeiros 10 registros da tabela que não estão entre os 20 primeiros — resultando nos registros 21 a 30.
Importante: para esse resultado fazer sentido, é essencial usar ORDER BY tanto na consulta externa quanto na subquery, com o mesmo critério de ordenação nas duas. Sem isso, o SQL Server não garante nenhuma ordem específica de retorno, e “os 20 primeiros” pode variar a cada execução.
O exemplo usa apenas o campo codigo, mas a mesma lógica funciona com quantos campos forem necessários — o importante é que a coluna usada no NOT IN seja a mesma usada para ordenar.
Limitação dessa abordagem
Essa técnica funciona bem quando a coluna usada no NOT IN tem valores únicos (como uma chave primária). Se houver valores repetidos nessa coluna, o filtro pode excluir ou incluir registros a mais, já que o NOT IN compara pelo valor, não pela posição da linha.
A forma moderna e recomendada: OFFSET ... FETCH
Desde o SQL Server 2012, existe uma sintaxe nativa para paginação, mais clara e sem as limitações do TOP + subquery:
SELECT codigo
FROM tbwebmundi
ORDER BY codigo
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;
Aqui a leitura é direta: pule (OFFSET) os 20 primeiros registros e pegue (FETCH NEXT) os 10 seguintes — exatamente os registros 21 a 30. Essa é hoje a forma recomendada para esse tipo de consulta, sendo mais legível e funcionando corretamente mesmo com valores repetidos na coluna de ordenação, desde que o critério de ORDER BY seja suficiente para dar uma ordem determinística (dica: inclua a chave primária no ORDER BY se o critério principal permitir empates).
Alternativa com ROW_NUMBER() (útil em versões mais antigas ou para casos mais complexos)
Antes do OFFSET/FETCH existir, e ainda hoje em cenários mais elaborados (como paginação combinada com filtros dinâmicos), é comum usar ROW_NUMBER():
SELECT codigo
FROM (
SELECT codigo, ROW_NUMBER() OVER (ORDER BY codigo) AS linha
FROM tbwebmundi
) AS resultado
WHERE linha BETWEEN 21 AND 30;
Generalizando para paginação (página 3, 10 por página, por exemplo)
DECLARE @PaginaAtual INT = 3;
DECLARE @RegistrosPorPagina INT = 10;
SELECT codigo
FROM tbwebmundi
ORDER BY codigo
OFFSET (@PaginaAtual - 1) * @RegistrosPorPagina ROWS
FETCH NEXT @RegistrosPorPagina ROWS ONLY;
Conclusão
A combinação de TOP com subquery é uma solução criativa e ainda didática para entender o problema, mas hoje em dia o caminho recomendado é OFFSET ... FETCH, disponível desde o SQL Server 2012 — mais simples de escrever, mais fácil de generalizar para paginação e sem as armadilhas de valores repetidos que o NOT IN pode causar. Em qualquer uma das abordagens, o ORDER BY é indispensável: sem ele, “os registros 21 a 30” não têm significado garantido.