Först till kvarn:du kan använd resultat från en CTE flera gånger i samma fråga, det är en huvudfunktion i CTE .) Det du har skulle fungera så här (medan du fortfarande använder CTE endast en gång):
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
Varning 1:rank()
rank()
kan returnera flera rader per person_id
med rank = 1
. DISTINCT ON (person_id)
(som Gordon tillhandahåller) är en tillämplig ersättning för row_number()
- som fungerar för dig, som ytterligare information förtydligas. Se:
Varning 2:ORDER BY submission_date DESC
Varken submission_date
inte heller last_updated
är definierade NOT NULL
. Kan vara ett problem med ORDER BY submission_date DESC, last_updated DESC ...
Se:
Skulle dessa kolumner verkligen vara NOT NULL
?
Du svarade:
Tomma strängar är inte tillåtna för typen date
. Håll kolumnerna nullbara. NULL
är det rätta värdet för dessa fall. Använd NULLS LAST
som visats för att undvika NULL
sorteras ovanpå.
Varning 3:OFFSET
Om OFFSET
är lika med eller större än antalet rader som returneras av CTE får du ingen rad , så heller ingen totalräkning. Se:
Interimslösning
Om vi tar upp alla varningar hittills och baserat på tillagd information kan vi komma fram till den här frågan:
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 är CTE faktiskt använd två gånger. RIGHT JOIN
garanterar att vi får det totala antalet, oavsett OFFSET
. DISTINCT ON
bör fungera OK-ish för de enda få raderna per (person_id)
i basfrågan.
Men du har breda rader. Hur bred i genomsnitt? Frågan kommer sannolikt att resultera i en sekventiell genomsökning av hela tabellen. Index hjälper inte (mycket). Allt detta kommer att förbli enormt ineffektivt för personsökning . Se:
Du kan inte involvera ett index för personsökning eftersom det är baserat på den härledda tabellen från CTE. Och dina faktiska sorteringskriterier för personsökning är fortfarande oklara (ORDER BY id
?). Om personsökning är målet behöver du desperat en annan frågestil. Om du bara är intresserad av de första sidorna behöver du ändå en annan frågestil. Den bästa lösningen beror på information som fortfarande saknas i frågan ...
Radikalt snabbare
För ditt uppdaterade mål:
(Ignorerar "för specificerade filterkriterier, typ, plan, status" för enkelhetens skull.)
Och:
Baserat på dessa två specialiserade index :
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 den här frågan:
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;
Varje uppsättning parenteser här krävs.
Denna nivå av sofistikering bör hämta en relativt liten uppsättning översta rader radikalt snabbare genom att använda de givna indexen och ingen sekventiell skanning. Se:
submission_date
bör troligen vara typ timestamptz
eller date
, inte - vilket är en udda typdefinition i Postgres i alla fall. Se:character varying(255)
Många fler detaljer kan optimeras, men det här håller på att gå ur händerna. Du kan överväga professionell rådgivning.