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 - wat sowieso een vreemde typedefinitie is in Postgres. Zie:character varying(255)
Er kunnen nog veel details worden geoptimaliseerd, maar dit loopt uit de hand. U kunt professioneel advies overwegen.