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 - was in Postgres sowieso eine seltsame Typdefinition ist. Siehe:character varying(255)
Viele weitere Details könnten optimiert werden, aber das gerät aus den Fugen. Ziehen Sie eine professionelle Beratung in Betracht.