Per prima cosa:tu puoi utilizzare i risultati di un CTE più volte nella stessa query, questa è una funzione di CTE .) Quello che hai funzionerebbe in questo modo (mentre usi ancora il CTE una sola volta):
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
Avvertimento 1:rank()
rank()
può restituire più righe per person_id
con rank = 1
. DISTINCT ON (person_id)
(come fornito da Gordon) è un sostituto applicabile per row_number()
- che funziona per te, come ulteriori informazioni chiarite. Vedi:
Attenzione 2:ORDER BY submission_date DESC
Né submission_date
né last_updated
sono definiti NOT NULL
. Può essere un problema con ORDER BY submission_date DESC, last_updated DESC ...
Vedi:
Queste colonne dovrebbero essere davvero NOT NULL
?
Hai risposto:
Non sono consentite stringhe vuote per il tipo date
. Mantieni le colonne annullabili. NULL
è il valore corretto per quei casi. Usa NULLS LAST
come dimostrato per evitare NULL
ordinato in cima.
Avvertimento 3:OFFSET
Se OFFSET
è uguale o maggiore del numero di righe restituite dal CTE, ottieni nessuna riga , quindi anche nessun conteggio totale. Vedi:
Soluzione provvisoria
Affrontando tutti gli avvertimenti finora e sulla base delle informazioni aggiunte, potremmo arrivare a questa domanda:
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;
Ora il CTE è in realtà usato due volte. Il RIGHT JOIN
garantisce che otteniamo il conteggio totale, indipendentemente dal OFFSET
. DISTINCT ON
dovrebbe funzionare in modo corretto per le poche righe per (person_id)
nella query di base.
Ma hai righe larghe. Quanto è largo in media? La query risulterà probabilmente in una scansione sequenziale sull'intera tabella. Gli indici non aiuteranno (molto). Tutto ciò rimarrà estremamente inefficiente per il paging . Vedi:
Non è possibile coinvolgere un indice per il paging poiché è basato sulla tabella derivata dal CTE. E i tuoi criteri di ordinamento effettivi per il paging non sono ancora chiari (ORDER BY id
?). Se il paging è l'obiettivo, hai un disperato bisogno di uno stile di query diverso. Se sei interessato solo alle prime pagine, hai ancora bisogno di uno stile di query diverso. La soluzione migliore dipende dalle informazioni ancora mancanti nella domanda...
Radialmente più veloce
Per il tuo obiettivo aggiornato:
(Ignorando "per criteri di filtro, tipo, piano, stato specificati" per semplicità.)
E:
Basato su questi due indici specializzati :
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);
Esegui questa query:
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;
Ogni serie di parentesi qui è obbligatoria.
Questo livello di sofisticazione dovrebbe recuperare un insieme relativamente piccolo di righe superiori in modo radicalmente più veloce utilizzando gli indici forniti e nessuna scansione sequenziale. Vedi:
submission_date
molto probabilmente dovrebbe essere di tipo timestamptz
o date
, non - che in ogni caso è una definizione di tipo dispari in Postgres. Vedi:character varying(255)
Molti altri dettagli potrebbero essere ottimizzati, ma questo sta sfuggendo di mano. Potresti prendere in considerazione una consulenza professionale.