Menu

LIMIT i OFFSET w SQLite: paginacja i wycinanie wyników

Jak działają LIMIT i OFFSET w SQLite: ograniczanie liczby wierszy, pomijanie wierszy, bezpieczna paginacja i pułapka wydajności w dużych tabelach.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

LIMIT ogranicza liczbę wierszy

LIMIT to najprostsze pokrętło w SQL: mówi SQLite „daj mi najwyżej tyle wierszy”. Dopisz je na końcu SELECT, a dostaniesz najwyżej tyle wyników, nie więcej, a może mniej, jeśli tabela nie ma wystarczająco wielu wierszy.

Dostajesz z powrotem pierwsze trzy wiersze. Tylko które dokładnie? W tym haczyk: bez ORDER BY SQLite wybiera kolejność, jaka jest dla niego wygodna. Dziś może to być kolejność wstawiania, jutro, po aktualizacji albo zmianie indeksu, już nie. Samo LIMIT wystarcza do „pokaż mi próbkę”, ale gdy tylko kolejność ma znaczenie, musisz ją podać jawnie.

OFFSET pomija wiersze od początku

Połącz LIMIT z OFFSET, a możesz poprosić o wycinek ze środka wyniku. OFFSET k odrzuca pierwsze k wierszy, a LIMIT n zwraca potem najwyżej n wierszy z tego, co zostało.

To „pomiń dwa wiersze, zwróć dwa kolejne”, czyli wiersze 3 i 4 posortowanego wyniku. Model myślowy: WHERE filtruje, ORDER BY sortuje, OFFSET pomija, LIMIT ogranicza. Działają w tej kolejności i każde ma znaczenie.

Paginacja zawsze wymaga ORDER BY

Najczęstsze zastosowanie LIMIT i OFFSET to paginacja: dzielenie długiej listy na strony po, powiedzmy, 20 wierszy. Strona 1 to LIMIT 20 OFFSET 0, strona 2 to LIMIT 20 OFFSET 20 i tak dalej.

Warto zauważyć dwie rzeczy. Po pierwsze, ORDER BY nie podlega negocjacjom: bez niego „strona 2” nie ma określonego znaczenia, a wiersze mogą się przetasować między kolejnymi wczytaniami strony. Po drugie, klucz sortowania zawiera id do rozstrzygania remisów. Jeśli dwa posty mają to samo created_at, potrzebujesz unikalnej kolumny, która nada im deterministyczną kolejność. Inaczej ich pozycje mogą się zamienić, a wiersz może przeskoczyć na inną stronę.

Praktyczna zasada: sortuj po czymś unikalnym albo po swojej kolumnie sortowania plus unikalnej kolumnie rozstrzygającej.

Skrót: LIMIT n, m

SQLite obsługuje starszą składnię z przecinkiem dla zgodności z MySQL: LIMIT offset, count. Znaczy to samo co LIMIT count OFFSET offset, ale kolejność jest odwrócona i łatwo ją źle odczytać.

-- Te dwa zapytania są równoważne:
SELECT * FROM books LIMIT 10 OFFSET 20;
SELECT * FROM books LIMIT 20, 10;     -- najpierw offset, potem liczba wierszy

Druga forma jest zwięzła, ale łapie ludzi, którzy spodziewają się, że pierwsza liczba to liczba wierszy. Trzymaj się LIMIT n OFFSET k: jest jawne i czyta się od lewej do prawej.

OFFSET bez LIMIT: sztuczka z LIMIT -1

OFFSET nie jest poprawne samo w sobie: gramatyka SQLite wymaga, żeby występowało po LIMIT. Jak więc powiedzieć „pomiń pierwsze 10 wierszy i daj mi wszystko dalej”? Przyjęło się pisać LIMIT -1, co SQLite odczytuje jako „bez górnej granicy”.

Każde ujemne LIMIT działa tak samo, ale -1 to utarty idiom. Zobaczysz go głównie w skryptach, które stronicują wynik i przy ostatniej porcji potrzebują zapytania „daj mi resztę”.

Pułapka wydajności OFFSET

Oto rzecz, o której nikt nie wspomina, dopóki na nią nie trafisz: OFFSET nie sprawia, że SQLite pomija pracę, tylko że pomija wynik. Żeby zwrócić wiersze od 10 001 do 10 020, silnik i tak wewnętrznie przechodzi przez pierwsze dziesięć tysięcy wierszy, zanim zacznie coś zwracać. Małe offsety nic nie kosztują, a offsety rzędu dziesiątek czy setek tysięcy wyraźnie zwalniają.

Przy głębokiej paginacji standardowym rozwiązaniem jest paginacja kluczem (keyset pagination): zamiast „pomiń N wierszy” zapamiętaj klucz sortowania ostatniego wiersza i poproś o „wiersze po tym”.

Każda strona wykonuje wyszukiwanie w indeksie zamiast przechodzić przez wszystko, co było wcześniej. Cena: nie przeskoczysz do „strony 47”, możesz iść przez dane tylko do przodu. Przy nieskończonym przewijaniu i kursorach w API to dokładnie to, czego chcesz.

Paginacja oparta na OFFSET sprawdza się w tabelach administracyjnych i przy małych zbiorach wyników. Przy wszystkim, co rośnie bez ograniczeń, sięgaj po paginację kluczem.

Przykład w praktyce

Wszystko razem: zapytanie z paginacją, filtrowaniem, sortowaniem i deterministycznym rozstrzyganiem remisów:

Zawęź do produktów biurowych, posortuj rosnąco po cenie z nazwą jako rozstrzygnięciem remisów i weź dwa pierwsze. Zmień OFFSET 0 na OFFSET 2, żeby dostać stronę 2. Zapytanie jest krótkie, ale każda klauzula ma swoje zadanie.

Dalej: DISTINCT

LIMIT decyduje, ile wierszy wraca, a DISTINCT decyduje, czy w ogóle wracają duplikaty. To kolejna klauzula z zestawu narzędzi SELECT i zaskakująco łatwo użyć jej źle. O tym na następnej stronie.

Najczęściej zadawane pytania

Co robi LIMIT w SQLite?

LIMIT n ogranicza liczbę wierszy zwracanych przez SELECT do najwyżej n. Działa po WHERE, GROUP BY i ORDER BY, więc ograniczasz końcowy zbiór wyników, a nie wiersze przeglądane przez zapytanie. SELECT * FROM users LIMIT 10 zwraca najwyżej dziesięć wierszy.

Jak działa OFFSET razem z LIMIT w SQLite?

OFFSET k pomija pierwsze k wierszy wyniku, zanim LIMIT zacznie liczyć. LIMIT 10 OFFSET 20 zwraca więc wiersze od 21 do 30. SQLite i tak musi wewnętrznie przejść przez pominięte wiersze, dlatego duże wartości offsetu działają wolno.

Czy można użyć OFFSET bez LIMIT w SQLite?

Nie bezpośrednio: OFFSET jest poprawne tylko jako część klauzuli LIMIT. Obejście to LIMIT -1 OFFSET k, gdzie -1 oznacza „bez górnej granicy”, więc SQLite pomija k wierszy i zwraca wszystko, co jest dalej. To dziwactwo, które warto zapamiętać.

Dlaczego zapytania z paginacją potrzebują ORDER BY?

Bez ORDER BY SQLite może zwracać wiersze w dowolnej kolejności, a ta kolejność może się zmieniać między zapytaniami. Wtedy paginacja się psuje: ten sam wiersz może pojawić się na stronie 1 i 3 albo całkiem zniknąć. Zawsze łącz LIMIT/OFFSET z ORDER BY po stabilnej, unikalnej kolumnie.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ