Uruchom zapytanie z LIMITEM / PRZESUNIĘCIEM, a także uzyskaj całkowitą liczbę wierszy


89

Do celów paginacji potrzebuję uruchomić zapytanie z klauzulami LIMITi OFFSET. Ale potrzebuję również liczby wierszy, które zostaną zwrócone przez to zapytanie bez klauzul LIMITi OFFSET.

Chcę biec:

SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?

I:

SELECT COUNT(*) FROM table WHERE /* whatever */

W tym samym czasie. Czy istnieje sposób, aby to zrobić, w szczególności sposób, który pozwala Postgresowi zoptymalizować go, aby był szybszy niż uruchamianie obu indywidualnie?


Odpowiedzi:


167

Tak. Dzięki prostej funkcji okna:

SELECT *, count(*) OVER() AS full_count
FROM   tbl
WHERE  /* whatever */
ORDER  BY col1
OFFSET ?
LIMIT  ?

Należy pamiętać, że koszt będzie znacznie wyższy niż bez całkowitej liczby, ale zazwyczaj nadal będzie tańszy niż dwa oddzielne zapytania. Postgres musi faktycznie policzyć wszystkie wiersze w obie strony, co narzuca koszt w zależności od całkowitej liczby kwalifikujących się wierszy. Detale:

Jednak , jak zauważył Dani , gdy OFFSETjest co najmniej tak duża, jak liczba wierszy zwróconych z zapytania podstawowego, żadne wiersze nie są zwracane. Więc my też nie dostajemy full_count.

Jeśli to nie do zaakceptowania, możliwym obejściem, aby zawsze zwracać pełną liczbę, byłoby użycie CTE i OUTER JOIN:

WITH cte AS (
   SELECT *
   FROM   tbl
   WHERE  /* whatever */
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY col1
   LIMIT  ?
   OFFSET ?
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(full_count) ON true;

Otrzymasz jeden wiersz wartości NULL z full_countdołączonym jeśli OFFSETjest za duży. W przeciwnym razie jest dołączany do każdego wiersza, tak jak w pierwszym zapytaniu.

Jeśli wiersz zawierający wszystkie wartości NULL jest możliwym prawidłowym wynikiem, należy sprawdzić, offset >= full_countaby ujednoznacznić pochodzenie pustego wiersza.

To nadal wykonuje zapytanie podstawowe tylko raz. Ale dodaje więcej narzutu do zapytania i płaci tylko wtedy, gdy to mniej niż powtórzenie zapytania podstawowego dla liczby.

Jeśli dostępne są indeksy obsługujące ostateczny porządek sortowania, może się opłacić uwzględnienie ORDER BYw CTE (nadmiarowo).


3
Zarówno przez LIMIT, jak i na warunkach, mamy wiersze do zwrócenia, ale z podanym przesunięciem nie zwróciłoby to żadnego wyniku. W takiej sytuacji, jak moglibyśmy uzyskać liczbę wierszy?
Dani Mathew

bardzo ładnie, dziękuję, działa świetnie, gdy używasz paginacji, zbiorów danych, po prostu dodaj to na początku swojego sql i użyj go, zapisz dodatkowe zapytanie o całkowitą liczbę.
Ahmed Sunny

Czy mógłbyś to rozwinąć, gdyby zliczanie można było włączyć dynamicznie w zapytaniu za pomocą parametru wejściowego? Mam podobne wymaganie, ale użytkownik decyduje, czy chce liczyć w wierszu, czy nie.
julealgon

1
@julealgon: Zacznij nowe pytanie, określając szczegóły. Zawsze możesz dodać link do tego, aby uzyskać kontekst i dodać komentarz tutaj, aby połączyć się z powrotem (i zwrócić moją uwagę), jeśli chcesz.
Erwin Brandstetter

1
@JustinL .: Dodatkowe obciążenie powinno być znaczące tylko w przypadku stosunkowo tanich zapytań podstawowych. Ponadto Postgres 12 poprawił wydajność CTE na wiele sposobów. (Chociaż ten CTE jest nadal MATERIALIZEDdomyślnie, przywoływany dwukrotnie.)
Erwin Brandstetter

1

edycja: ta odpowiedź jest poprawna podczas pobierania niefiltrowanej tabeli. Pozwolę na to na wypadek, gdyby mogło to komuś pomóc, ale może nie odpowiedzieć dokładnie na początkowe pytanie.

Odpowiedź Erwina Brandstettera jest idealna, jeśli potrzebujesz dokładnej wartości. Jednak na dużych stołach często potrzebujesz tylko całkiem dobrego przybliżenia. Postgres właśnie to daje i będzie znacznie szybszy, ponieważ nie będzie musiał oceniać każdego wiersza:

SELECT *
FROM (
    SELECT *
    FROM tbl
    WHERE /* something */
    ORDER BY /* something */
    OFFSET ?
    LIMIT ?
    ) data
RIGHT JOIN (SELECT reltuples FROM pg_class WHERE relname = 'tbl') pg_count(total_count) ON true;

Właściwie nie jestem pewien, czy istnieje korzyść z eksternalizacji RIGHT JOINlub posiadania go tak, jak w standardowym zapytaniu. Zasługiwałoby na testy.

SELECT t.*, pgc.reltuples AS total_count
FROM tbl as t
RIGHT JOIN pg_class pgc ON pgc.relname = 'tbl'
WHERE /* something */
ORDER BY /* something */
OFFSET ?
LIMIT ?

2
O szybkim szacowaniu liczby: stackoverflow.com/a/7945274/939860 Tak jak powiedziałeś: ważne podczas pobierania całej tabeli - co jest sprzeczne z WHEREklauzulą ​​w twoich zapytaniach. Drugie zapytanie jest logicznie błędne (pobiera jeden wiersz dla każdej tabeli w bazie danych) - i droższe, gdy zostanie naprawione.
Erwin Brandstetter

-7

Jest to zła praktyka wywoływanie dwa razy tego samego zapytania w celu uzyskania całkowitej liczby wierszy wyniku końcowego. Zajmie to czas wykonania i zmarnuje zasoby serwera.

Lepiej, możesz użyć SQL_CALC_FOUND_ROWSw zapytaniu, które powie MySQL, aby pobierał całkowitą liczbę wierszy wraz z limitami wyników zapytania.

Przykład ustawiony jako:

SELECT SQL_CALC_FOUND_ROWS employeeName, phoneNumber FROM employee WHERE employeeName LIKE 'a%' LIMIT 10;

SELECT FOUND_ROWS();

W powyższym zapytaniu wystarczy dodać SQL_CALC_FOUND_ROWSopcję w pozostałym wymaganym zapytaniu i wykonać drugą linię, tj. SELECT FOUND_ROWS()Zwraca liczbę wierszy w zestawie wyników zwróconych przez tę instrukcję.


1
Rozwiązanie wymaga postgres, a nie mysql.
MuffinMan

@MuffinMan, możesz użyć tego samego na mysql. Od MYSQL 4.0 jest używana opcja SQL_CALC_FOUND_ROWS w zapytaniu. Ale od MYSQL 8.0 jest usunięty.
Mohd Rashid

Nieistotne. Na to pytanie odpowiedziano lata temu. Jeśli chcesz wnieść swój wkład, opublikuj nowe pytanie na ten sam temat, ale specyficzne dla MySQL.
MuffinMan

zawsze na czasie
Ali Hussain

-14

Nie.

Być może jest pewien niewielki zysk, który teoretycznie można uzyskać, uruchamiając je pojedynczo z wystarczająco skomplikowaną maszyną pod maską. Ale jeśli chcesz wiedzieć, ile wierszy pasuje do warunku, musisz je policzyć, a nie tylko OGRANICZONY podzbiór.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.