sql >> Database teknologi >  >> RDS >> PostgreSQL

Få paginerede rækker og samlet antal i en enkelt forespørgsel

Første ting først:du kan bruge resultater fra en CTE flere gange i den samme forespørgsel, det er en hovedfunktion i CTE'er .) Det, du har, ville fungere sådan her (mens du stadig kun bruger CTE én gang):

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

Advarsel 1:rank()

rank() kan returnere flere rækker pr. person_id med rank = 1 . DISTINCT ON (person_id) (som Gordon leverede) er en anvendelig erstatning for row_number() - som virker for dig, som yderligere info præciseret. Se:

Advarsel 2:ORDER BY submission_date DESC

Hverken submission_date heller ikke last_updated er defineret NOT NULL . Kan være et problem med ORDER BY submission_date DESC, last_updated DESC ... Se:

Skulle disse kolonner virkelig være NOT NULL ?

Du svarede:

Tomme strenge er ikke tilladt for typen date . Hold kolonnerne nullable. NULL er den rigtige værdi for disse tilfælde. Brug NULLS LAST som vist for at undgå NULL bliver sorteret ovenpå.

Advarsel 3:OFFSET

Hvis OFFSET er lig med eller større end antallet af rækker, der returneres af CTE, får du ingen række , så heller ingen totaloptælling. Se:

Foreløbig løsning

Tager vi alle forbehold indtil videre, og baseret på tilføjet information, kan vi komme frem til denne forespørgsel:

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 er CTE faktisk brugt to gange. RIGHT JOIN garanterer, at vi får det samlede antal, uanset OFFSET . DISTINCT ON bør udføre OK-ish for de kun få rækker pr. (person_id) i basisforespørgslen.

Men du har brede rækker. Hvor bred i gennemsnit? Forespørgslen vil sandsynligvis resultere i en sekventiel scanning af hele tabellen. Indekser hjælper ikke (meget). Alt dette vil forblive meget ineffektivt til personsøgning . Se:

Du kan ikke involvere et indeks for personsøgning, da det er baseret på den afledte tabel fra CTE. Og dine faktiske sorteringskriterier for personsøgning er stadig uklare (ORDER BY id ?). Hvis personsøgning er målet, har du desperat brug for en anden forespørgselsstil. Hvis du kun er interesseret i de første par sider, har du stadig brug for en anden forespørgselsstil. Den bedste løsning afhænger af oplysninger, der stadig mangler i spørgsmålet ...

Radikalt hurtigere

Til dit opdaterede mål:

(Ignorerer "for specificerede filterkriterier, type, plan, status" for nemheds skyld.)

Og:

Baseret på disse to specialiserede indeks :

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 denne forespørgsel:

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;

Hvert sæt parenteser her er påkrævet.

Dette sofistikerede niveau skulle hente et relativt lille sæt af øverste rækker radikalt hurtigere ved at bruge de givne indekser og ingen sekventiel scanning. Se:

submission_date skal højst sandsynligt være typen timestamptz eller date , ikke character varying(255) - hvilket er en mærkelig type definition i Postgres under alle omstændigheder. Se:

Mange flere detaljer kan være optimeret, men det er ved at løbe ud af hænderne. Du kan overveje professionel rådgivning.



  1. Hvorfor opstår der en ORA-12054-fejl, når du opretter dette simple materialiserede visningseksempel?

  2. Øger indstillingen NOT NULL på en kolonne i postgresql ydeevnen?

  3. Udfør forespørgsel baseret på den JSON, der er gemt i kolonnen

  4. SQL-udvikler til import fra Excel