LIMIT e OFFSET: paginação

1. Introdução à Paginação em SQL

Paginação é a técnica de dividir um grande conjunto de resultados em partes menores e gerenciáveis, chamadas de páginas. Em aplicações web, APIs e relatórios, raramente faz sentido retornar milhares ou milhões de registros de uma só vez — isso sobrecarrega o banco de dados, consome memória do servidor e degrada a experiência do usuário.

Sem paginação, uma consulta como SELECT * FROM vendas poderia trazer 5 milhões de linhas, causando timeouts e lentidão generalizada. Com paginação, recuperamos apenas 20 ou 50 registros por vez.

As cláusulas LIMIT e OFFSET são a abordagem mais simples e difundida para implementar paginação em SQL. LIMIT define quantas linhas retornar, enquanto OFFSET especifica quantas linhas pular antes de começar a retornar.

2. Sintaxe Básica do LIMIT

A sintaxe fundamental é:

SELECT colunas
FROM tabela
LIMIT n;

Isso retorna as primeiras n linhas da consulta. Exemplo prático:

SELECT nome, email
FROM clientes
LIMIT 5;

Resultado: os 5 primeiros clientes da tabela.

Em bancos como MySQL, PostgreSQL e SQLite, a sintaxe é idêntica. No SQL Server, a cláusula equivalente é SELECT TOP n, mas o padrão mais moderno (a partir do SQL Server 2012) usa OFFSET...FETCH, como veremos adiante.

3. Sintaxe Básica do OFFSET

OFFSET sozinho não faz sentido — ele precisa de LIMIT (ou FETCH) para funcionar:

SELECT colunas
FROM tabela
LIMIT n
OFFSET m;

Isso pula as primeiras m linhas e retorna as próximas n. Exemplo:

SELECT nome, email
FROM clientes
LIMIT 3
OFFSET 2;

Aqui, pulamos os 2 primeiros clientes e retornamos os 3 seguintes (clientes 3, 4 e 5, assumindo ordenação padrão).

4. Paginação com LIMIT e OFFSET

A fórmula clássica para calcular os parâmetros é:

LIMIT tamanho_da_pagina
OFFSET (numero_da_pagina - 1) * tamanho_da_pagina

Exemplo completo com página de 10 registros:

Página 1:

SELECT id, nome, data_cadastro
FROM clientes
ORDER BY id
LIMIT 10 OFFSET 0;

Página 2:

SELECT id, nome, data_cadastro
FROM clientes
ORDER BY id
LIMIT 10 OFFSET 10;

Página 3:

SELECT id, nome, data_cadastro
FROM clientes
ORDER BY id
LIMIT 10 OFFSET 20;

Atenção: ORDER BY é obrigatório! Sem ele, a ordem dos registros não é garantida, e a mesma página pode mostrar dados diferentes em execuções distintas. Sempre ordene por uma coluna (ou combinação) que seja única ou estável.

5. Performance e Limitações

Embora simples, OFFSET tem um problema grave de performance em tabelas grandes. O banco de dados precisa ler e descartar todas as linhas até o ponto de OFFSET. Para OFFSET 1000000 com LIMIT 20, o banco lê 1.000.020 linhas, descarta as primeiras 1.000.000 e retorna apenas 20.

Isso causa:
- Uso intensivo de I/O e CPU
- Aumento do tempo de resposta conforme a página avança
- Travamentos em consultas com milhões de registros

Alternativa recomendada: paginação baseada em cursor (keyset pagination). Em vez de pular linhas, usamos a última chave da página anterior:

SELECT id, nome, data_cadastro
FROM clientes
WHERE id > 100   -- último id da página anterior
ORDER BY id
LIMIT 10;

Essa abordagem é muito mais eficiente, pois usa índices e não precisa escanear linhas descartadas. Para tabelas com milhões de registros, prefira essa técnica.

6. Combinação com ORDER BY e Filtros

Podemos combinar LIMIT/OFFSET com WHERE e ORDER BY livremente:

SELECT id, nome, total_gasto
FROM clientes
WHERE ativo = true
ORDER BY total_gasto DESC, id ASC
LIMIT 10 OFFSET 20;

Isso retorna a terceira página (registros 21 a 30) dos clientes ativos, ordenados do maior gastador para o menor.

Ordenação instável: Se dois registros tiverem o mesmo valor na coluna ORDER BY, a ordem entre eles pode variar entre execuções. Para garantir estabilidade, inclua uma coluna única como desempate (ex.: ORDER BY total_gasto DESC, id ASC).

7. Variações entre SGBDs

Cada banco tem sua sintaxe:

MySQL, PostgreSQL, SQLite:

SELECT * FROM produtos
ORDER BY preco
LIMIT 5 OFFSET 10;

SQL Server (2012+):

SELECT *
FROM produtos
ORDER BY preco
OFFSET 10 ROWS
FETCH NEXT 5 ROWS ONLY;

Oracle (antes da versão 12c):

SELECT * FROM (
    SELECT p.*, ROWNUM rnum
    FROM (
        SELECT * FROM produtos ORDER BY preco
    ) p
    WHERE ROWNUM <= 15
)
WHERE rnum > 10;

Oracle (12c+): Suporta OFFSET...FETCH similar ao SQL Server.

DB2 e Firebird: Também usam OFFSET...FETCH ou LIMIT...OFFSET dependendo da versão.

8. Boas Práticas e Casos de Uso

Quando usar OFFSET:
- Tabelas pequenas (até algumas centenas de milhares de registros)
- Prototipagem rápida
- Sistemas internos com baixo volume de dados

Quando evitar OFFSET:
- Tabelas com milhões de registros
- APIs públicas com requisitos de performance
- Relatórios com navegação profunda (página 100+)

Implementação de "carregar mais" (infinite scroll): Prefira paginação por cursor. A cada requisição, envie o último id ou created_at visto e use WHERE chave > valor com LIMIT.

Dicas para testes:
- Sempre valide que o número total de registros está correto (use COUNT(*) separado)
- Teste páginas extremas (primeira, última, além do fim)
- Verifique se a ordenação está estável com dados duplicados

Referências