PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Uzyskaj podzielone na strony wiersze i całkowitą liczbę w jednym zapytaniu

Po pierwsze:możesz używać wyników z CTE wiele razy w tym samym zapytaniu, jest to główna cecha CTE .) To, co masz, działałoby w ten sposób (przy jednoczesnym użyciu CTE tylko raz):

WITH cte AS (
   SELECT * FROM (
      SELECT *, row_number()  -- see below
                OVER (PARTITION BY person_id
                      ORDER BY submission_date DESC NULLS LAST  -- see below
                             , last_updated DESC NULLS LAST  -- see below
                             , id DESC) AS rn
      FROM  tbl
      ) sub
   WHERE  rn = 1
   AND    status IN ('ACCEPTED', 'CORRECTED')
   )
SELECT *, count(*) OVER () AS total_rows_in_cte
FROM   cte
LIMIT  10
OFFSET 0;  -- see below

Zastrzeżenie 1:rank()

rank() może zwrócić wiele wierszy na person_id z rank = 1 . DISTINCT ON (person_id) (jak podał Gordon) jest odpowiednim zamiennikiem dla row_number() - co działa dla Ciebie, jak wyjaśniono dodatkowe informacje. Zobacz:

Zastrzeżenie 2:ORDER BY submission_date DESC

Ani submission_date ani last_updated są zdefiniowane NOT NULL . Może być problem z ORDER BY submission_date DESC, last_updated DESC ... Zobacz:

Czy te kolumny naprawdę powinny być NOT NULL? ?

Odpowiedziałeś:

Puste ciągi nie są dozwolone dla typu date . Zachowaj wartości null w kolumnach. NULL jest odpowiednią wartością dla tych przypadków. Użyj NULLS LAST jak pokazano, aby uniknąć NULL sortowane na górze.

Zastrzeżenie 3:OFFSET

Jeśli OFFSET jest równa lub większa niż liczba wierszy zwracanych przez CTE, otrzymujesz brak wiersza , więc też nie ma całkowitej liczby. Zobacz:

Rozwiązanie tymczasowe

Odnosząc się do wszystkich dotychczasowych zastrzeżeń i na podstawie dodanych informacji, możemy dojść do tego zapytania:

WITH cte AS (
   SELECT DISTINCT ON (person_id) *
   FROM   tbl
   WHERE  status IN ('ACCEPTED', 'CORRECTED')
   ORDER  BY person_id, submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY person_id  -- ?? see below
   LIMIT  10
   OFFSET 0
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(total_rows_in_cte) ON true;

Teraz CTE jest właściwie używane dwukrotnie. RIGHT JOIN gwarantuje, że uzyskamy całkowitą liczbę, bez względu na OFFSET . DISTINCT ON powinien wykonać OK dla tylko kilku wierszy na (person_id) w zapytaniu podstawowym.

Ale masz szerokie rzędy. Jaka jest średnia szerokość? Zapytanie prawdopodobnie spowoduje sekwencyjne skanowanie całej tabeli. Indeksy nie pomogą (dużo). Wszystko to pozostanie bardzo nieefektywne w przypadku stronicowania . Zobacz:

Nie można włączyć indeksu do stronicowania, ponieważ jest on oparty na tabeli pochodnej z CTE. Twoje rzeczywiste kryteria sortowania stronicowania są nadal niejasne (ORDER BY id ?). Jeśli celem jest stronicowanie, desperacko potrzebujesz innego stylu zapytania. Jeśli interesują Cię tylko pierwsze strony, potrzebujesz jeszcze innego stylu zapytania. Najlepsze rozwiązanie zależy od informacji, których wciąż brakuje w pytaniu...

Radykalnie szybciej

Dla zaktualizowanego celu:

(Ignorowanie „dla określonych kryteriów filtrowania, typu, planu, stanu” dla uproszczenia.)

Oraz:

Na podstawie tych dwóch wyspecjalizowanych indeksów :

CREATE INDEX ON tbl (submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST)
WHERE  status IN ('ACCEPTED', 'CORRECTED'); -- optional

CREATE INDEX ON tbl (person_id, submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST);

Uruchom to zapytanie:

WITH RECURSIVE cte AS (
   (
   SELECT t  -- whole row
   FROM   tbl t
   WHERE  status IN ('ACCEPTED', 'CORRECTED')
   AND    NOT EXISTS (SELECT FROM tbl
                      WHERE  person_id = t.person_id 
                      AND   (  submission_date,   last_updated,   id)
                          > (t.submission_date, t.last_updated, t.id)  -- row-wise comparison
                      )
   ORDER  BY submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST
   LIMIT  1
   )

   UNION ALL
   SELECT (SELECT t1  -- whole row
           FROM   tbl t1
           WHERE ( t1.submission_date, t1.last_updated, t1.id)
               < ((t).submission_date,(t).last_updated,(t).id)  -- row-wise comparison
           AND    t1.status IN ('ACCEPTED', 'CORRECTED')
           AND    NOT EXISTS (SELECT FROM tbl
                              WHERE  person_id = t1.person_id 
                              AND   (   submission_date,    last_updated,    id)
                                  > (t1.submission_date, t1.last_updated, t1.id)  -- row-wise comparison
                              )
           ORDER  BY submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST
           LIMIT  1)
   FROM   cte c
   WHERE  (t).id IS NOT NULL
   )
SELECT (t).*
FROM   cte
LIMIT  10
OFFSET 0;

Każdy zestaw nawiasów jest tutaj wymagany.

Ten poziom zaawansowania powinien radykalnie szybciej pobrać stosunkowo mały zestaw górnych wierszy przy użyciu podanych wskaźników i bez skanowania sekwencyjnego. Zobacz:

submission_date powinno być najprawdopodobniej wpisać timestamptz lub date , a nie character varying(255) - co w każdym przypadku jest nieparzystą definicją typu w Postgresie. Zobacz:

Można zoptymalizować znacznie więcej szczegółów, ale to wymyka się z rąk. Możesz rozważyć profesjonalne doradztwo.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Kubernetes timescaledb statefulset:Zmiany utracone podczas odtwarzania pod

  2. BŁĄD:błąd składni przy lub blisko OVER

  3. PostgreSQL:istnieje kontra lewe dołączenie

  4. PostgreSQL — musi występować w klauzuli GROUP BY lub być używany w funkcji agregującej

  5. [Wideo] Ansible i PostgreSQL