Paginacja jest często używana w aplikacjach, w których użytkownik może kliknąć Poprzedni /Dalej aby poruszać się po stronach składających się na wyniki lub kliknij numer strony, aby przejść bezpośrednio do określonej strony.
Uruchamiając zapytania w SQL Server, możesz podzielić wyniki na strony za pomocą OFFSET
i FETCH
argumenty ORDER BY
klauzula. Te argumenty zostały wprowadzone w SQL Server 2012, dlatego możesz użyć tej techniki, jeśli masz SQL Server 2012 lub nowszy.
W tym kontekście podział na strony polega na podzieleniu wyników zapytania na mniejsze porcje, przy czym każda porcja jest kontynuowana w miejscu, w którym zakończyła się poprzednia. Na przykład, jeśli zapytanie zwraca 1000 wierszy, można je podzielić na strony, tak aby były zwracane w grupach po 100. Aplikacja może przekazać numer strony i rozmiar strony do programu SQL Server, a program SQL Server może następnie użyć ich do zwrócenia tylko dane dla żądanej strony.
Przykład 1 – brak stronicowania
Najpierw uruchommy zapytanie, które zwróci wszystkie wiersze w tabeli:
SELECT * FROM Genres ORDER BY GenreId;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 1 | Rock | | 2 | Jazz | | 3 | Country | | 4 | Pop | | 5 | Blues | | 6 | Hip Hop | | 7 | Rap | | 8 | Punk | +-----------+---------+
W tym przykładzie nie zastosowano paginacji – wszystkie wyniki są wyświetlane.
Ten zestaw wyników jest tak mały, że normalnie nie wymagałby stronicowania, ale na potrzeby tego artykułu podzielmy go na strony.
Przykład 2 – Wyświetl pierwsze 3 wyniki
Ten przykład wyświetla pierwsze trzy wyniki:
SELECT * FROM Genres ORDER BY GenreId OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 1 | Rock | | 2 | Jazz | | 3 | Country | +-----------+---------+
W takim przypadku określam, że wyniki powinny zaczynać się od pierwszego wyniku i wyświetlać kolejne trzy wiersze. Odbywa się to w następujący sposób:
OFFSET 0 ROWS
określa, że nie powinno być żadnego przesunięcia (przesunięcie równe zero).FETCH NEXT 3 ROWS ONLY
pobiera następne trzy wiersze od przesunięcia. Ponieważ określiłem przesunięcie równe zero, pobierane są pierwsze trzy wiersze.
Gdyby wszystko, czego chcieliśmy, to 3 najlepsze wyniki, moglibyśmy osiągnąć ten sam wynik, używając TOP
klauzula zamiast określania wartości przesunięcia i pobrania. Jednak to nie pozwoliłoby nam na wykonanie następnej części.
Przykład 3 – Wyświetl kolejne 3 wyniki
Teraz wyświetlmy kolejne trzy wyniki:
SELECT * FROM Genres ORDER BY GenreId OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 4 | Pop | | 5 | Blues | | 6 | Hip Hop | +-----------+---------+
Jedyną rzeczą, którą zmieniłem, było przesunięcie.
Wartości przesunięcia i pobierania mogą być również wyrażeniem dostarczonym jako zmienna, parametr lub stałe podzapytanie skalarne. Gdy używane jest podzapytanie, nie może ono odwoływać się do żadnych kolumn zdefiniowanych w zewnętrznym zakresie zapytania (nie może być skorelowane z zapytaniem zewnętrznym).
Poniższe przykłady używają wyrażeń, aby pokazać dwa podejścia do stronicowania wyników.
Przykład 4 – Podział na strony według numeru wiersza
W tym przykładzie użyto wyrażeń do określenia wiersza numer, od którego należy zacząć.
DECLARE @StartRow int = 1, @RowsPerPage int = 3; SELECT * FROM Genres ORDER BY GenreId ASC OFFSET @StartRow - 1 ROWS FETCH NEXT @RowsPerPage ROWS ONLY;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 1 | Rock | | 2 | Jazz | | 3 | Country | +-----------+---------+
Tutaj używam @StartRow int = 1
aby określić, że wyniki powinny zaczynać się od pierwszego wiersza.
Oto, co się stanie, jeśli zwiększę tę wartość do 2
.
DECLARE @StartRow int = 2, @RowsPerPage int = 3; SELECT * FROM Genres ORDER BY GenreId ASC OFFSET @StartRow - 1 ROWS FETCH NEXT @RowsPerPage ROWS ONLY;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 2 | Jazz | | 3 | Country | | 4 | Pop | +-----------+---------+
Zaczyna się w drugim rzędzie. Korzystając z tej metody, mogę określić dokładny wiersz, od którego mam zacząć.
Przykład 5 – Podział na strony według numeru strony
Ten przykład jest prawie identyczny z poprzednim przykładem, z wyjątkiem tego, że pozwala określić numer strony, a nie numer wiersza.
DECLARE @PageNumber int = 1, @RowsPerPage int = 3; SELECT * FROM Genres ORDER BY GenreId ASC OFFSET (@PageNumber - 1) * @RowsPerPage ROWS FETCH NEXT @RowsPerPage ROWS ONLY;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 1 | Rock | | 2 | Jazz | | 3 | Country | +-----------+---------+
Więc pierwszy wynik jest taki sam. Zobaczmy jednak, co się stanie, gdy zwiększymy @PageNumber
do 2
(Zmieniłem nazwę tej zmiennej, aby odzwierciedlić jej nowy cel).
DECLARE @PageNumber int = 2, @RowsPerPage int = 3; SELECT * FROM Genres ORDER BY GenreId ASC OFFSET (@PageNumber - 1) * @RowsPerPage ROWS FETCH NEXT @RowsPerPage ROWS ONLY;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 4 | Pop | | 5 | Blues | | 6 | Hip Hop | +-----------+---------+
Tym razem wyniki zaczynają się od czwartego rzędu. Używając tej metody, możesz po prostu przekazać numer strony zamiast numeru wiersza.
Przykład 6 – pętla stronicowania
Aby zakończyć, oto krótki przykład, który przechodzi przez wszystkie strony i określa początkowy numer wiersza dla każdej iteracji:
DECLARE @StartRow int = 1, @RowsPerPage int = 3; WHILE (SELECT COUNT(*) FROM Genres) >= @StartRow BEGIN SELECT * FROM Genres ORDER BY GenreId ASC OFFSET @StartRow - 1 ROWS FETCH NEXT @RowsPerPage ROWS ONLY; SET @StartRow = @StartRow + @RowsPerPage; CONTINUE END;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 1 | Rock | | 2 | Jazz | | 3 | Country | +-----------+---------+ (3 rows affected) +-----------+---------+ | GenreId | Genre | |-----------+---------| | 4 | Pop | | 5 | Blues | | 6 | Hip Hop | +-----------+---------+ (3 rows affected) +-----------+---------+ | GenreId | Genre | |-----------+---------| | 7 | Rap | | 8 | Punk | +-----------+---------+ (2 rows affected)
Przykład 7 – ROW vs ROWS
Jeśli napotkasz kod, który używa ROW
zamiast ROWS
, oba argumenty robią to samo. Są synonimami i zapewniają zgodność z ANSI.
Oto pierwszy przykład na tej stronie, ale z ROW
zamiast ROWS
.
SELECT * FROM Genres ORDER BY GenreId OFFSET 0 ROW FETCH NEXT 3 ROW ONLY;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 1 | Rock | | 2 | Jazz | | 3 | Country | +-----------+---------+
Przykład 8 – PIERWSZY kontra NASTĘPNY
To samo dotyczy FIRST
i NEXT
. Są to synonimy zapewniające zgodność z ANSI.
Oto poprzedni przykład, ale z FIRST
zamiast NEXT
.
SELECT * FROM Genres ORDER BY GenreId OFFSET 0 ROW FETCH FIRST 3 ROW ONLY;
Wynik:
+-----------+---------+ | GenreId | Genre | |-----------+---------| | 1 | Rock | | 2 | Jazz | | 3 | Country | +-----------+---------+