Po pierwsze:możesz używać wyników z CTE wiele razy w tym samym zapytaniu, jest to główna cecha CTE .) To, co masz, działałoby w ten sposób (przy jednoczesnym użyciu CTE tylko raz):
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
Zastrzeżenie 1:rank()
rank()
może zwrócić wiele wierszy na person_id
z rank = 1
. DISTINCT ON (person_id)
(jak podał Gordon) jest odpowiednim zamiennikiem dla row_number()
- co działa dla Ciebie, jak wyjaśniono dodatkowe informacje. Zobacz:
Zastrzeżenie 2:ORDER BY submission_date DESC
Ani submission_date
ani last_updated
są zdefiniowane NOT NULL
. Może być problem z ORDER BY submission_date DESC, last_updated DESC ...
Zobacz:
Czy te kolumny naprawdę powinny być NOT NULL
? ?
Odpowiedziałeś:
Puste ciągi nie są dozwolone dla typu date
. Zachowaj wartości null w kolumnach. NULL
jest odpowiednią wartością dla tych przypadków. Użyj NULLS LAST
jak pokazano, aby uniknąć NULL
sortowane na górze.
Zastrzeżenie 3:OFFSET
Jeśli OFFSET
jest równa lub większa niż liczba wierszy zwracanych przez CTE, otrzymujesz brak wiersza , więc też nie ma całkowitej liczby. Zobacz:
Rozwiązanie tymczasowe
Odnosząc się do wszystkich dotychczasowych zastrzeżeń i na podstawie dodanych informacji, możemy dojść do tego zapytania:
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;
Teraz CTE jest właściwie używane dwukrotnie. RIGHT JOIN
gwarantuje, że uzyskamy całkowitą liczbę, bez względu na OFFSET
. DISTINCT ON
powinien wykonać OK dla tylko kilku wierszy na (person_id)
w zapytaniu podstawowym.
Ale masz szerokie rzędy. Jaka jest średnia szerokość? Zapytanie prawdopodobnie spowoduje sekwencyjne skanowanie całej tabeli. Indeksy nie pomogą (dużo). Wszystko to pozostanie bardzo nieefektywne w przypadku stronicowania . Zobacz:
Nie można włączyć indeksu do stronicowania, ponieważ jest on oparty na tabeli pochodnej z CTE. Twoje rzeczywiste kryteria sortowania stronicowania są nadal niejasne (ORDER BY id
?). Jeśli celem jest stronicowanie, desperacko potrzebujesz innego stylu zapytania. Jeśli interesują Cię tylko pierwsze strony, potrzebujesz jeszcze innego stylu zapytania. Najlepsze rozwiązanie zależy od informacji, których wciąż brakuje w pytaniu...
Radykalnie szybciej
Dla zaktualizowanego celu:
(Ignorowanie „dla określonych kryteriów filtrowania, typu, planu, stanu” dla uproszczenia.)
Oraz:
Na podstawie tych dwóch wyspecjalizowanych indeksów :
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);
Uruchom to zapytanie:
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;
Każdy zestaw nawiasów jest tutaj wymagany.
Ten poziom zaawansowania powinien radykalnie szybciej pobrać stosunkowo mały zestaw górnych wierszy przy użyciu podanych wskaźników i bez skanowania sekwencyjnego. Zobacz:
submission_date
powinno być najprawdopodobniej wpisać timestamptz
lub date
, a nie - co w każdym przypadku jest nieparzystą definicją typu w Postgresie. Zobacz:character varying(255)
Można zoptymalizować znacznie więcej szczegółów, ale to wymyka się z rąk. Możesz rozważyć profesjonalne doradztwo.