sql >> Databáze >  >> RDS >> PostgreSQL

Získejte stránkované řádky a celkový počet v jednom dotazu

Za prvé:můžete použít výsledky z CTE vícekrát ve stejném dotazu, to je hlavní funkce CTE .) To, co máte, by fungovalo takto (zatímco stále používáte CTE pouze jednou):

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

Upozornění 1:rank()

rank() může vrátit více řádků na person_id s rank = 1 . DISTINCT ON (person_id) (jako za předpokladu Gordona) je použitelnou náhradou za row_number() - který pro vás funguje, jak je objasněno další informace. Viz:

Upozornění 2:ORDER BY submission_date DESC

Ani submission_date ani last_updated jsou definovány NOT NULL . Může se jednat o problém s ORDER BY submission_date DESC, last_updated DESC ... Viz:

Měly by být tyto sloupce skutečně NOT NULL ?

Odpověděli jste:

Pro typ date nejsou povoleny prázdné řetězce . Ponechte sloupce s možností null. NULL je správná hodnota pro tyto případy. Použijte NULLS LAST jak bylo ukázáno, aby se zabránilo NULL řazení nahoře.

Upozornění 3:OFFSET

Pokud OFFSET je stejný nebo větší než počet řádků vrácených CTE, získáte žádný řádek , takže také žádný celkový počet. Viz:

Prozatímní řešení

Po vyřešení všech dosavadních výhrad a na základě přidaných informací bychom mohli dospět k tomuto dotazu:

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;

Nyní je CTE ve skutečnosti použit dvakrát. RIGHT JOIN zaručuje, že získáme celkový počet bez ohledu na OFFSET . DISTINCT ON by měl provést OK-ish pouze pro několik řádků na (person_id) v základním dotazu.

Ale máte široké řady. Jak široký v průměru? Dotaz pravděpodobně povede k sekvenčnímu skenování celé tabulky. Indexy nepomohou (moc). To vše zůstane pro stránkování značně neefektivní . Viz:

Nemůžete zahrnout index pro stránkování, protože je založen na odvozené tabulce z CTE. A vaše skutečná kritéria řazení pro stránkování jsou stále nejasná (ORDER BY id ?). Pokud je cílem stránkování, zoufale potřebujete jiný styl dotazu. Pokud vás zajímá pouze prvních pár stránek, potřebujete jiný styl dotazu. Nejlepší řešení závisí na informacích, které v otázce stále chybí ...

Radikálně rychlejší

Pro váš aktualizovaný cíl:

(Ignorování „pro zadaná kritéria filtru, typ, plán, stav“ pro jednoduchost.)

A:

Na základě těchto dvou specializovaných indexů :

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);

Spusťte tento dotaz:

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ždá sada závorek zde je povinná.

Tato úroveň sofistikovanosti by měla získat relativně malou sadu horních řádků radikálně rychleji pomocí daných indexů a bez sekvenčního skenování. Viz:

submission_date by s největší pravděpodobností měl být typ timestamptz nebo date , nikoli character varying(255) - což je v Postgresu v každém případě zvláštní definice typu. Viz:

Mnoho dalších detailů by mohlo být optimalizováno, ale to se vymyká z rukou. Můžete zvážit odborné konzultace.



  1. Syntaxe Oracle – měli bychom si vybrat mezi starým a novým?

  2. jak nastavit výstup serveru pomocí připojení jdbc v Jmeter

  3. Oracle exportuje SQL struktury databáze

  4. Offset řádků v SQL Server