Antes de mais nada:você pode usar os resultados de um CTE várias vezes na mesma consulta, esse é o principal recurso principal de CTEs .) O que você tem funcionaria assim (enquanto ainda usa o CTE apenas uma vez):
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
Aviso 1:rank()
rank()
pode retornar várias linhas por person_id
com rank = 1
. DISTINCT ON (person_id)
(como Gordon fornecido) é um substituto aplicável para row_number()
- que funciona para você, conforme informações adicionais esclarecidas. Ver:Aviso 2:ORDER BY submission_date DESC
Nem
submission_date
nem last_updated
são definidos NOT NULL
. Pode ser um problema com ORDER BY submission_date DESC, last_updated DESC ...
Ver:Essas colunas realmente devem ser
NOT NULL
? Você respondeu:
Strings vazias não são permitidas para o tipo
date
. Mantenha as colunas anuláveis. NULL
é o valor adequado para esses casos. Use NULLS LAST
conforme demonstrado para evitar NULL
sendo ordenado por cima. Aviso 3:OFFSET
Se
OFFSET
for igual ou maior que o número de linhas retornado pelo CTE, você obterá nenhuma linha , portanto, também não há contagem total. Ver:Solução provisória
Abordando todas as ressalvas até agora e com base nas informações adicionadas, podemos chegar a esta consulta:
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;
Agora o CTE é realmente usado duas vezes. O
RIGHT JOIN
garante que obteremos a contagem total, não importa o OFFSET
. DISTINCT ON
deve executar OK para as poucas linhas por (person_id)
na consulta básica. Mas você tem linhas largas. Qual a largura em média? A consulta provavelmente resultará em uma varredura sequencial em toda a tabela. Índices não vão ajudar (muito). Tudo isso continuará sendo extremamente ineficiente para paginação . Ver:
Você não pode envolver um índice para paginação, pois é baseado na tabela derivada do CTE. E seus critérios de classificação reais para paginação ainda não estão claros (
ORDER BY id
?). Se a paginação é o objetivo, você precisa desesperadamente de um estilo de consulta diferente. Se você estiver interessado apenas nas primeiras páginas, ainda precisará de um estilo de consulta diferente. A melhor solução depende de informações que ainda faltam na pergunta... Radualmente mais rápido
Para seu objetivo atualizado:
(Ignorando "para critérios de filtro especificados, tipo, plano, status" Pela simplicidade.)
E:
Com base nesses dois índices especializados :
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);
Execute esta consulta:
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;
Cada conjunto de parênteses aqui é obrigatório.
Esse nível de sofisticação deve recuperar um conjunto relativamente pequeno de linhas superiores radicalmente mais rápido usando os índices fornecidos e sem varredura sequencial. Ver:
submission_date
provavelmente deve ser o tipo timestamptz
ou date
, não character varying(255)
Muitos outros detalhes podem ser otimizados, mas isso está ficando fora de controle. Você pode considerar a consultoria profissional.