PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Rufen Sie paginierte Zeilen und die Gesamtzahl in einer einzigen Abfrage ab

Das Wichtigste zuerst:Sie können Ergebnisse aus einem CTE mehrmals in derselben Abfrage verwenden, das ist ein Hauptmerkmal von CTEs .) Was Sie haben, würde so funktionieren (während Sie den CTE nur einmal verwenden):

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

Hinweis 1:rank()

rank() kann mehrere Zeilen pro person_id zurückgeben mit rank = 1 . DISTINCT ON (person_id) (wie von Gordon bereitgestellt) ist ein geeigneter Ersatz für row_number() - was für Sie funktioniert, wie zusätzliche Informationen geklärt haben. Siehe:

Vorsicht 2:ORDER BY submission_date DESC

Weder submission_date noch last_updated sind NOT NULL definiert . Kann ein Problem mit ORDER BY submission_date DESC, last_updated DESC ... sein Siehe:

Sollten diese Spalten wirklich NOT NULL sein ?

Sie haben geantwortet:

Leere Zeichenfolgen sind für den Typ date nicht zulässig . Lassen Sie die Spalten nullfähig. NULL ist der richtige Wert für diese Fälle. Verwenden Sie NULLS LAST wie demonstriert, um NULL zu vermeiden oben einsortiert.

Warnhinweis 3:OFFSET

Wenn OFFSET gleich oder größer als die Anzahl der vom CTE zurückgegebenen Zeilen ist, erhalten Sie keine Zeile , also auch keine Gesamtzahl. Siehe:

Zwischenlösung

Unter Berücksichtigung aller bisherigen Vorbehalte und basierend auf zusätzlichen Informationen könnten wir zu dieser Abfrage gelangen:

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;

Jetzt ist der CTE eigentlich zweimal verwendet. Der RIGHT JOIN garantiert, dass wir die Gesamtzahl erhalten, unabhängig vom OFFSET . DISTINCT ON sollte für die wenigen Zeilen pro (person_id) OK funktionieren in der Basisabfrage.

Aber Du hast breite Reihen. Wie breit im Durchschnitt? Die Abfrage führt wahrscheinlich zu einem sequentiellen Scan der gesamten Tabelle. Indizes werden nicht (viel) helfen. All dies wird äußerst ineffizient für das Paging bleiben . Siehe:

Sie können keinen Index zum Paging einbeziehen, da dieser auf der abgeleiteten Tabelle aus dem CTE basiert. Und Ihr tatsächliches Sortierkriterium für das Paging ist immer noch unklar (ORDER BY id ?). Wenn Paging das Ziel ist, brauchen Sie dringend einen anderen Abfragestil. Wenn Sie nur an den ersten Seiten interessiert sind, benötigen Sie noch einen anderen Abfragestil. Die beste Lösung hängt von Informationen ab, die in der Frage noch fehlen ...

Radikal schneller

Für Ihr aktualisiertes Ziel:

(Ignorieren von "für angegebene Filterkriterien, Typ, Plan, Status" der Einfachheit halber.)

Und:

Basierend auf diesen beiden spezialisierten Indizes :

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

Führen Sie diese Abfrage aus:

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;

Alle Klammern hier sind erforderlich.

Dieser Grad an Raffinesse sollte einen relativ kleinen Satz oberster Zeilen radikal schneller abrufen, indem die angegebenen Indizes und kein sequenzieller Scan verwendet werden. Siehe:

submission_date sollte höchstwahrscheinlich vom Typ timestamptz sein oder date , nicht character varying(255) - was in Postgres sowieso eine seltsame Typdefinition ist. Siehe:

Viele weitere Details könnten optimiert werden, aber das gerät aus den Fugen. Ziehen Sie eine professionelle Beratung in Betracht.