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 - hvilket er en mærkelig type definition i Postgres under alle omstændigheder. Se:character varying(255)
Mange flere detaljer kan være optimeret, men det er ved at løbe ud af hænderne. Du kan overveje professionel rådgivning.