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 - que es una definición de tipo extraño en Postgres en cualquier caso. Ver:character varying(255)
Es posible que se optimicen muchos más detalles, pero esto se está yendo de las manos. Podría considerar una consultoría profesional.