PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Obtenha linhas paginadas e contagem total em uma única consulta


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) - que é uma definição de tipo ímpar no Postgres em qualquer caso. Ver:

Muitos outros detalhes podem ser otimizados, mas isso está ficando fora de controle. Você pode considerar a consultoria profissional.