LIMIT controla quantas linhas voltam
O LIMIT é o ajuste mais direto do SQL: ele diz pro SQLite "me devolve no máximo essa quantidade de linhas". É só colocar no fim de um SELECT e você recebe até esse número de resultados — nem mais, e às vezes menos, caso a tabela não tenha linhas suficientes pra atender ao pedido.
Você recebe as três primeiras linhas. Mas quais três, exatamente? Aí está a pegadinha: sem um ORDER BY, o SQLite devolve as linhas na ordem que achar mais conveniente. Hoje pode ser a ordem de inserção; amanhã, depois de um update ou de uma mudança de índice, pode ser outra coisa. Usar LIMIT sozinho até funciona quando você só quer "dar uma olhada na amostra", mas, no instante em que a ordem importa, você precisa deixá-la explícita.
OFFSET pula linhas a partir do início
Combinando LIMIT com OFFSET, dá pra pegar uma fatia do meio do resultado. O OFFSET k descarta as primeiras k linhas; em seguida, o LIMIT n retorna no máximo n linhas do que sobrou.
Ou seja: "pula duas linhas e devolve as duas seguintes" — as linhas 3 e 4 do resultado ordenado. O modelo mental é esse: WHERE filtra, ORDER BY ordena, OFFSET pula e LIMIT corta. Eles rodam nessa ordem, e todos importam.
Paginação no SQLite sempre precisa de ORDER BY
O uso mais comum de LIMIT com OFFSET é a paginação — dividir uma lista longa em páginas de, digamos, 20 linhas cada. A página 1 fica LIMIT 20 OFFSET 0, a página 2 vira LIMIT 20 OFFSET 20, e assim por diante.
Dois detalhes que merecem atenção. Primeiro: o ORDER BY não é opcional — sem ele, "página 2" simplesmente não tem significado definido, e as linhas podem trocar de lugar entre uma requisição e outra. Segundo: a chave de ordenação inclui o id como critério de desempate. Se dois posts tiverem o mesmo created_at, você precisa de uma coluna única para garantir uma ordem determinística, senão a posição deles pode oscilar e uma mesma linha pode aparecer em duas páginas diferentes.
Regra prática: ordene por algo único, ou então ordene pela sua coluna de ordenação somada a um critério único de desempate.
Atalho: LIMIT n, m
O SQLite aceita uma sintaxe antiga, com vírgula, por compatibilidade com o MySQL: LIMIT offset, count. O efeito é o mesmo de LIMIT count OFFSET offset, mas a ordem dos argumentos é invertida — e isso é fácil de ler errado.
-- Estas duas são equivalentes:
SELECT * FROM books LIMIT 10 OFFSET 20;
SELECT * FROM books LIMIT 20, 10; -- offset primeiro, depois a quantidade
A segunda forma é mais enxuta, mas costuma pegar de surpresa quem espera que o primeiro número seja a quantidade. Fique com LIMIT n OFFSET k — é explícito e lê da esquerda para a direita.
OFFSET sem LIMIT no SQLite: o truque do LIMIT -1
Usar OFFSET sozinho não funciona — a gramática do SQLite exige que ele venha sempre depois de um LIMIT. Então, como dizer "pula as 10 primeiras linhas e me devolve o resto"? A convenção é usar LIMIT -1, que o SQLite interpreta como "sem limite máximo".
Qualquer LIMIT negativo faz a mesma coisa, mas -1 é o idioma consagrado. Você costuma ver isso em scripts que paginam um resultado e querem uma consulta do tipo "me devolve o resto" para o último lote.
A armadilha de performance do OFFSET
Aqui vai o que ninguém comenta até você se queimar: o OFFSET não faz o SQLite pular trabalho, ele faz o SQLite pular saída. Para retornar as linhas de 10.001 a 10.020, o engine ainda percorre internamente as primeiras dez mil linhas antes de começar a emitir resultados. Offsets pequenos saem de graça; offsets na casa das dezenas ou centenas de milhares ficam visivelmente lentos.
Para paginação profunda, a solução padrão é a keyset pagination (paginação por chave): em vez de "pular N linhas", você guarda a chave de ordenação da última linha e pede "as linhas depois desta".
Cada página faz uma busca indexada em vez de varrer tudo o que veio antes. O preço a pagar: você não consegue pular direto para a "página 47" — só dá pra avançar pelos dados. Para feeds de scroll infinito e cursores de API, é exatamente isso que você quer.
A paginação com OFFSET resolve bem em telas administrativas e conjuntos de resultados pequenos. Mas, quando a tabela cresce sem limite, vale partir para a paginação por keyset.
Um exemplo na prática
Juntando tudo — uma consulta paginada com filtro, ordenação e um critério de desempate determinístico:
Filtre apenas os produtos da categoria escritório, ordene por preço crescente usando o nome como critério de desempate e pegue os dois primeiros. Para ir para a página 2, troque OFFSET 0 por OFFSET 2. A consulta é curta, mas cada cláusula tem seu papel.
A seguir: DISTINCT
O LIMIT define quantas linhas voltam; já o DISTINCT decide se valores duplicados aparecem ou não. É a próxima cláusula do arsenal do SELECT e, apesar de parecer simples, é fácil de usar errado — é o que veremos na próxima página.
Perguntas frequentes
O que o LIMIT faz no SQLite?
LIMIT n restringe o número de linhas que um SELECT retorna a, no máximo, n. Ele é aplicado depois de WHERE, GROUP BY e ORDER BY, ou seja, você está limitando o resultado final, e não as linhas que a consulta varre internamente. Por exemplo, SELECT * FROM users LIMIT 10 devolve até dez linhas.
Como o OFFSET funciona junto com o LIMIT no SQLite?
OFFSET k pula as primeiras k linhas do resultado antes do LIMIT começar a contar. Então LIMIT 10 OFFSET 20 retorna as linhas 21 a 30. O detalhe é que o SQLite ainda precisa percorrer essas linhas puladas internamente — é por isso que offsets muito grandes ficam lentos.
Dá para usar OFFSET sem LIMIT no SQLite?
Diretamente, não — o OFFSET só é válido como parte de uma cláusula LIMIT. A saída é usar LIMIT -1 OFFSET k, onde -1 significa 'sem limite superior'. Assim o SQLite pula k linhas e devolve o restante. É uma peculiaridade que vale a pena guardar.
Por que consultas paginadas precisam de ORDER BY?
Sem ORDER BY, o SQLite pode retornar as linhas na ordem que quiser, e essa ordem pode mudar entre execuções. Aí a paginação quebra: a mesma linha aparece na página 1 e na 3, ou some de vez. Sempre combine LIMIT/OFFSET com um ORDER BY em uma coluna estável e única.