sql >> Database >  >> RDS >> PostgreSQL

Krijg gepagineerde rijen en totaal aantal in één zoekopdracht

Allereerst:u kunt resultaten van een CTE meerdere keren in dezelfde zoekopdracht gebruiken, dat is een belangrijke functie van CTE's .) Wat je hebt, zou als volgt werken (terwijl je de CTE nog maar één keer gebruikt):

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

Voorbehoud 1:rank()

rank() kan meerdere rijen retourneren per person_id met rank = 1 . DISTINCT ON (person_id) (zoals Gordon aangaf) is een toepasselijke vervanging voor row_number() - wat voor u werkt, zoals aanvullende informatie verduidelijkt. Zie:

Voorbehoud 2:ORDER BY submission_date DESC

Geen van beide submission_date noch last_updated zijn gedefinieerd NOT NULL . Kan een probleem zijn met ORDER BY submission_date DESC, last_updated DESC ... Zie:

Moeten die kolommen echt NOT NULL zijn? ?

Je antwoordde:

Lege strings zijn niet toegestaan ​​voor type date . Houd de kolommen nullable. NULL is de juiste waarde voor die gevallen. Gebruik NULLS LAST zoals aangetoond om NULL te vermijden bovenaan gesorteerd.

Voorbehoud 3:OFFSET

Als OFFSET gelijk is aan of groter is dan het aantal rijen dat door de CTE wordt geretourneerd, krijgt u geen rij , dus ook geen totaaltelling. Zie:

Tussentijdse oplossing

Als we alle waarschuwingen tot nu toe aanpakken en op basis van toegevoegde informatie, kunnen we tot deze vraag komen:

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 is de CTE eigenlijk twee keer gebruikt. De RIGHT JOIN garandeert dat we het totale aantal krijgen, ongeacht de OFFSET . DISTINCT ON zou OK-achtig moeten presteren voor de enige paar rijen per (person_id) in de basisquery.

Maar je hebt brede rijen. Hoe breed gemiddeld? De query zal waarschijnlijk resulteren in een sequentiële scan van de hele tabel. Indexen zullen niet (veel) helpen. Dit alles blijft enorm inefficiënt voor paging . Zie:

U kunt geen index voor paging betrekken omdat die gebaseerd is op de afgeleide tabel uit de CTE. En uw werkelijke sorteercriteria voor paging zijn nog steeds onduidelijk (ORDER BY id ?). Als paging het doel is, hebt u dringend een andere vraagstijl nodig. Als u alleen geïnteresseerd bent in de eerste paar pagina's, heeft u een andere zoekstijl nodig. De beste oplossing hangt af van de informatie die nog ontbreekt in de vraag ...

Radicaal sneller

Voor uw bijgewerkte doelstelling:

(Negeren van "voor gespecificeerde filtercriteria, type, plan, status" voor de eenvoud.)

En:

Gebaseerd op deze twee gespecialiseerde indexen :

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

Voer deze zoekopdracht uit:

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;

Elke set haakjes hier is vereist.

Dit niveau van verfijning zou een relatief kleine set bovenste rijen radicaal sneller moeten ophalen door de gegeven indices te gebruiken en geen sequentiële scan. Zie:

submission_date moet hoogstwaarschijnlijk timestamptz . zijn of date , niet character varying(255) - wat sowieso een vreemde typedefinitie is in Postgres. Zie:

Er kunnen nog veel details worden geoptimaliseerd, maar dit loopt uit de hand. U kunt professioneel advies overwegen.



  1. Lui ophalen van enkele kolom (klassekenmerk) met Hibernate

  2. SELECT rijen met minimaal aantal (*)

  3. MYSQL-volgorde op leuk/niet leuk en populariteit

  4. Databaseontwerp voor meertalige toepassingen