sql >> Base de Datos >  >> RDS >> PostgreSQL

Obtenga filas paginadas y recuento total en una sola consulta

Lo primero es lo primero:usted puede use los resultados de un CTE varias veces en la misma consulta, esa es una característica principal de CTE .) Lo que tiene funcionaría así (mientras todavía usa el CTE solo una 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

Advertencia 1:rank()

rank() puede devolver varias filas por person_id con rank = 1 . DISTINCT ON (person_id) (como proporcionó Gordon) es un reemplazo aplicable para row_number() - que funciona para usted, como se aclaró información adicional. Ver:

Advertencia 2:ORDER BY submission_date DESC

Ni submission_date ni last_updated están definidos NOT NULL . Puede ser un problema con ORDER BY submission_date DESC, last_updated DESC ... Ver:

¿Deberían esas columnas realmente ser NOT NULL? ?

Respondiste:

No se permiten cadenas vacías para el tipo date . Mantenga las columnas anulables. NULL es el valor adecuado para esos casos. Utilice NULLS LAST como se demostró para evitar NULL siendo ordenados en la parte superior.

Advertencia 3:OFFSET

Si OFFSET es igual o mayor que el número de filas devueltas por el CTE, obtendrá ninguna fila , por lo que tampoco hay recuento total. Ver:

Solución provisional

Abordando todas las advertencias hasta el momento, y según la información agregada, podríamos llegar 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;

Ahora el CTE es realmente usado dos veces. El RIGHT JOIN garantiza que obtengamos el recuento total, sin importar el OFFSET . DISTINCT ON debe realizar OK-ish para las pocas filas por (person_id) en la consulta base.

Pero tienes filas anchas. ¿Qué tan ancho en promedio? La consulta probablemente dará como resultado un escaneo secuencial en toda la tabla. Los índices no ayudarán (mucho). Todo esto seguirá siendo enormemente ineficiente para la paginación . Ver:

No puede involucrar un índice para la paginación ya que se basa en la tabla derivada del CTE. Y su criterio de clasificación real para la paginación aún no está claro (ORDER BY id ?). Si el objetivo es la paginación, necesita desesperadamente un estilo de consulta diferente. Si solo está interesado en las primeras páginas, aún necesita un estilo de consulta diferente. La mejor solución depende de la información que aún falta en la pregunta...

Radicalmente más rápido

Para su objetivo actualizado:

(Ignorando "para criterios de filtro especificados, tipo, plan, estado" por simplicidad.)

Y:

Basado en estos dos í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);

Ejecute 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;

Todos los paréntesis aquí son obligatorios.

Este nivel de sofisticación debería recuperar un conjunto relativamente pequeño de filas superiores radicalmente más rápido utilizando los índices proporcionados y sin exploración secuencial. Ver:

submission_date probablemente debería ser tipo timestamptz o date , no character varying(255) - que es una definición de tipo extraño en Postgres en cualquier caso. Ver:

Es posible que se optimicen muchos más detalles, pero esto se está yendo de las manos. Podría considerar una consultoría profesional.