Pobieranie łącznej liczby wierszy z OFFSET / FETCH NEXT

92

Mam więc funkcję, która zwraca liczbę rekordów, dla których chcę zaimplementować stronicowanie w mojej witrynie. Zasugerowano mi, żebym w tym celu użyć opcji Przesunięcie / pobranie dalej w programie SQL Server 2012. Na naszej stronie internetowej mamy obszar, który zawiera całkowitą liczbę rekordów i stronę, na której jesteś w danym momencie.

Wcześniej otrzymywałem cały zestaw rekordów i byłem w stanie programowo zbudować stronicowanie. Ale używając metody SQL z FETCH NEXT X ROWS TYLKO, otrzymuję tylko X wierszy, więc nie wiem, jaki jest mój całkowity zestaw rekordów i jak obliczyć moje strony minimalne i maksymalne. Jedynym sposobem, w jaki mogę to zrobić, jest dwukrotne wywołanie funkcji i zliczenie wierszy w pierwszym, a następnie uruchomienie drugiego z poleceniem FETCH NEXT. Czy jest lepszy sposób, w którym nie będę musiał dwukrotnie uruchamiać zapytania? Próbuję przyspieszyć działanie, a nie zwolnić.

Krystaliszny niebieski
źródło

Odpowiedzi:

115

Możesz użyć COUNT(*) OVER()... tutaj jest szybki przykład przy użyciu sys.all_objects:

DECLARE 
  @PageSize INT = 10, 
  @PageNum  INT = 1;

SELECT 
  name, object_id, 
  overall_count = COUNT(*) OVER()
FROM sys.all_objects
ORDER BY name
  OFFSET (@PageNum-1)*@PageSize ROWS
  FETCH NEXT @PageSize ROWS ONLY;

Należy to jednak zarezerwować dla małych zbiorów danych; na większych zestawach wydajność może być fatalna. Zobacz ten artykuł Paula White'a, aby uzyskać lepsze alternatywy , w tym utrzymywanie indeksowanych widoków (co działa tylko wtedy, gdy wynik jest niefiltrowany lub znasz WHEREz góry klauzule) i stosowanie ROW_NUMBER()sztuczek.

Aaron Bertrand
źródło
44
W tabeli zawierającej 3500000 rekordów LICZBA (*) OVER () zajęło 1 minutę i 3 sekundy. Podejście opisane poniżej przez Jamesa Moberga zajęło 13 sekund, aby pobrać ten sam zestaw danych. Jestem pewien, że metoda Count Over działa dobrze w przypadku mniejszych zestawów danych, ale kiedy zaczniesz robić się naprawdę duże, znacznie spowalnia.
matthew_360
Lub możesz po prostu użyć COUNT (1) OVER (), co jest o wiele szybsze, ponieważ nie musi czytać rzeczywistych danych z tabeli, tak jak robi to count (*)
ldx
1
@AaronBertrand Naprawdę? to musi oznaczać, że albo masz indeks, który zawiera wszystkie kolumny, albo że został on znacznie ulepszony od 2008R2. W tej wersji liczba (*) działa sekwencyjnie, co oznacza, że ​​najpierw * (jak w: wszystkie kolumny) jest wybierana, a następnie zliczana. Jeśli policzyłeś (1), po prostu wybierasz stałą, która jest o wiele szybsza niż odczyt rzeczywistych danych.
ldx
5
@idx Nie, przepraszam, to też nie działało w 2008 R2. Używam SQL Server od 6.5 i nie przypominam sobie czasu, kiedy silnik nie był wystarczająco inteligentny, aby po prostu przeskanować najwęższy indeks w poszukiwaniu COUNT (*) lub COUNT (1). Na pewno nie od 2000 roku. Ale hej, mam instancję 2008 R2, czy możesz skonfigurować kopię zapasową na SQLfiddle, która zademonstruje tę różnicę, o której twierdzisz, że istnieje? Cieszę się, że mogę tego spróbować.
Aaron Bertrand
2
w bazie danych SQL Server 2016, przeszukiwanie tabeli z około 25 milionami wierszy, stronicowanie ponad 3000 wyników (z kilkoma połączeniami, w tym do funkcji wartościowanej w tabeli), zajęło to milisekundy - super!
jkerak
142

Napotkałem pewne problemy z wydajnością przy użyciu metody COUNT ( ) OVER (). (Nie jestem pewien, czy był to serwer, ponieważ zwrócenie 10 rekordów zajęło 40 sekund, a później nie było żadnych problemów.) Ta technika działała we wszystkich warunkach bez konieczności użycia COUNT ( ) OVER () i realizuje ta sama rzecz:

DECLARE 
    @PageSize INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, Name
    FROM Table
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)
SELECT *
FROM TempResult, TempCount
ORDER BY TempResult.Name
    OFFSET (@PageNum-1)*@PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY
James Moberg
źródło
32
Byłoby naprawdę super, gdyby istniała możliwość zapisania wartości COUNT (*) do zmiennej. Byłbym w stanie ustawić go jako parametr OUTPUT mojej procedury składowanej. Jakieś pomysły?
Do Ka
1
Czy istnieje sposób, aby uzyskać liczbę w oddzielnej tabeli? Wygląda na to, że możesz użyć „TempResult” tylko dla pierwszej poprzedzającej instrukcji SELECT.
matthew_360
4
Dlaczego to działa tak dobrze? W pierwszym CTE wszystkie wiersze są zaznaczane, a następnie zmniejszane przez pobieranie. Domyśliłbym się, że wybranie wszystkich wierszy w pierwszym CTE znacznie spowolniłoby działanie. W każdym razie dziękuję za to!
jbd
1
w moim przypadku zwolnił niż COUNT (1) OVER () .. może dlatego, że funkcja w zaznaczeniu.
Tiju John,
1
Działa to idealnie w przypadku małej bazy danych, gdy wiersze są miliony, zajmuje to zbyt dużo czasu.
Kiya
1

Na podstawie odpowiedzi Jamesa Moberga :

Jest to alternatywne użycie Row_Number(), jeśli nie masz serwera SQL 2012 i nie możesz użyć PRZESUNIĘCIA

DECLARE 
    @PageNumEnd INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, NAME
    FROM Tabla
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)

select * 
from
(
    SELECT
     ROW_NUMBER() OVER ( ORDER BY PolizaId DESC) AS 'NumeroRenglon', 
     MaxRows, 
     ID,
     Name
    FROM TempResult, TempCount

)resultados
WHERE   NumeroRenglon >= @PageNum
    AND NumeroRenglon <= @PageNumEnd
ORDER BY NumeroRenglon
elblogdelbeto
źródło