Най-напред:виеможете използвайте резултатите от CTE многократно в една и съща заявка, това е основна характеристика на CTE .) Това, което имате, ще работи по следния начин (докато все още използвате CTE само веднъж):
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
Предупреждение 1:rank()
rank()
може да върне множество редове на person_id
с rank = 1
. DISTINCT ON (person_id)
(както Gordon предостави) е приложим заместител на row_number()
- което работи за вас, като допълнителна информация е изяснена. Вижте:
Предупреждение 2:ORDER BY submission_date DESC
Нито submission_date
нито last_updated
са дефинирани NOT NULL
. Може да има проблем с ORDER BY submission_date DESC, last_updated DESC ...
Вижте:
Трябва ли тези колони наистина да са NOT NULL
?
Вие отговорихте:
Празни низове не са разрешени за тип date
. Поддържайте колоните nullable. NULL
е правилната стойност за тези случаи. Използвайте NULLS LAST
както е показано, за да избегнете NULL
се сортира отгоре.
Предупреждение 3:OFFSET
Ако OFFSET
е равен или по-голям от броя на редовете, върнати от CTE, получавате няма ред , така че също няма общ брой. Вижте:
Временно решение
Като разгледаме всички предупреждения досега и въз основа на добавената информация, може да стигнем до тази заявка:
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;
Сега CTE е всъщност използвана два пъти. RIGHT JOIN
гарантира, че получаваме общия брой, независимо от OFFSET
. DISTINCT ON
трябва да изпълнява ОК за само няколко реда на (person_id)
в основната заявка.
Но имате широки редове. Колко широк е средно? Заявката вероятно ще доведе до последователно сканиране на цялата таблица. Индексите няма да помогнат (много). Всичко това ще остане изключително неефективно за страниране . Вижте:
Не можете да включите индекс за страниране, тъй като той се основава на извлечената таблица от CTE. И вашите действителни критерии за сортиране за страниране все още не са ясни (ORDER BY id
?). Ако странирането е целта, отчаяно се нуждаете от различен стил на заявка. Ако се интересувате само от първите няколко страници, все още имате нужда от различен стил на заявка. Най-доброто решение зависи от информацията, която все още липсва във въпроса ...
Радиално по-бързо
За вашата актуализирана цел:
(Игнориране на "за определени критерии за филтриране, тип, план, състояние" за простота.)
И:
Въз основа на тези два специализирани индекса :
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);
Изпълнете тази заявка:
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;
Всеки набор от скоби тук е задължителен.
Това ниво на сложност би трябвало да извлече относително малък набор от най-горни редове радикално по-бързо чрез използване на дадените индекси и без последователно сканиране. Вижте:
submission_date
най-вероятно трябва да е тип timestamptz
или date
, а не - което във всеки случай е дефиниция на нечетен тип в Postgres. Вижте:character varying(255)
Много повече подробности може да бъдат оптимизирани, но това излиза извън контрол. Може да обмислите професионална консултация.