sql >> Databasteknik >  >> RDS >> PostgreSQL

Få paginerade rader och totalt antal i en enda fråga

Först till kvarn:du kan använd resultat från en CTE flera gånger i samma fråga, det är en huvudfunktion i CTE .) Det du har skulle fungera så här (medan du fortfarande använder CTE endast en gång):

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

Varning 1:rank()

rank() kan returnera flera rader per person_id med rank = 1 . DISTINCT ON (person_id) (som Gordon tillhandahåller) är en tillämplig ersättning för row_number() - som fungerar för dig, som ytterligare information förtydligas. Se:

Varning 2:ORDER BY submission_date DESC

Varken submission_date inte heller last_updated är definierade NOT NULL . Kan vara ett problem med ORDER BY submission_date DESC, last_updated DESC ... Se:

Skulle dessa kolumner verkligen vara NOT NULL ?

Du svarade:

Tomma strängar är inte tillåtna för typen date . Håll kolumnerna nullbara. NULL är det rätta värdet för dessa fall. Använd NULLS LAST som visats för att undvika NULL sorteras ovanpå.

Varning 3:OFFSET

Om OFFSET är lika med eller större än antalet rader som returneras av CTE får du ingen rad , så heller ingen totalräkning. Se:

Interimslösning

Om vi ​​tar upp alla varningar hittills och baserat på tillagd information kan vi komma fram till den här frågan:

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;

Nu är CTE faktiskt använd två gånger. RIGHT JOIN garanterar att vi får det totala antalet, oavsett OFFSET . DISTINCT ON bör fungera OK-ish för de enda få raderna per (person_id) i basfrågan.

Men du har breda rader. Hur bred i genomsnitt? Frågan kommer sannolikt att resultera i en sekventiell genomsökning av hela tabellen. Index hjälper inte (mycket). Allt detta kommer att förbli enormt ineffektivt för personsökning . Se:

Du kan inte involvera ett index för personsökning eftersom det är baserat på den härledda tabellen från CTE. Och dina faktiska sorteringskriterier för personsökning är fortfarande oklara (ORDER BY id ?). Om personsökning är målet behöver du desperat en annan frågestil. Om du bara är intresserad av de första sidorna behöver du ändå en annan frågestil. Den bästa lösningen beror på information som fortfarande saknas i frågan ...

Radikalt snabbare

För ditt uppdaterade mål:

(Ignorerar "för specificerade filterkriterier, typ, plan, status" för enkelhetens skull.)

Och:

Baserat på dessa två specialiserade 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);

Kör den här frågan:

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;

Varje uppsättning parenteser här krävs.

Denna nivå av sofistikering bör hämta en relativt liten uppsättning översta rader radikalt snabbare genom att använda de givna indexen och ingen sekventiell skanning. Se:

submission_date bör troligen vara typ timestamptz eller date , inte character varying(255) - vilket är en udda typdefinition i Postgres i alla fall. Se:

Många fler detaljer kan optimeras, men det här håller på att gå ur händerna. Du kan överväga professionell rådgivning.



  1. MySQL ODBC misslyckas i C# .NET-projekt men inte runtime-kompilerad kod

  2. välj COUNT med tre tabeller - mysql

  3. Oracle söklista med ord i sträng och återställ befintliga

  4. hur man hämtar data från mysql-databasen med php