Перво-наперво: вы можете использовать результаты из 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)
(как и в случае с Гордоном) - это подходящая замена для 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
. Держите столбцы обнуляемыми. 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
должен выполнить OK-i sh только для нескольких строк на (person_id)
в базовом запросе.
Но у вас широкие строки. Насколько широко в среднем? Скорее всего, запрос приведет к последовательному сканированию всей таблицы. Индексы не помогут (сильно). Все это останется крайне неэффективным для подкачки . См .:
Невозможно задействовать индекс для подкачки, поскольку он основан на производной таблице из КТР. А ваши действительные критерии сортировки для подкачки пока неясны (ORDER BY id
?). Если целью является подкачка страниц, вам крайне необходим другой стиль запросов. Если вас интересуют только первые несколько страниц, вам нужен другой стиль запроса. Наилучшее решение зависит от информации, которая все еще отсутствует в вопросе ...
Радикально быстрее
Для вашей обновленной цели:
Найти последние записи для person_id
на submission_date
(Игнорирование "для указанных критериев фильтра, типа, плана, статуса" для простоты.)
И:
Найти последнюю строку для person_id
, только если она имеет status IN ('ACCEPTED','CORRECTED')
На основе этих двух специализированных индексов :
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
, а не character varying(255)
- что является странным определением типа в Postgres в любом случае. См .:
Многие дополнительные детали могут быть оптимизированы, но это выходит из-под контроля. Вы могли бы рассмотреть профессиональный консалтинг.