Tout d'abord :vous pouvez utiliser les résultats d'un CTE plusieurs fois dans la même requête, c'est une fonctionnalité principale de CTE .) Ce que vous avez fonctionnerait comme ceci (tout en utilisant le CTE une seule fois) :
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
Mise en garde 1 :rank()
rank()
peut renvoyer plusieurs lignes par person_id
avec rank = 1
. DISTINCT ON (person_id)
(comme Gordon fourni) est un remplacement applicable pour row_number()
- qui fonctionne pour vous, en tant qu'informations supplémentaires clarifiées. Voir :
Mise en garde 2 :ORDER BY submission_date DESC
Ni submission_date
ni last_updated
sont définis NOT NULL
. Peut être un problème avec ORDER BY submission_date DESC, last_updated DESC ...
Voir :
Ces colonnes doivent-elles vraiment être NOT NULL
?
Vous avez répondu :
Les chaînes vides ne sont pas autorisées pour le type date
. Gardez les colonnes nullables. NULL
est la valeur appropriée pour ces cas. Utilisez NULLS LAST
comme démontré pour éviter NULL
étant trié en haut.
Mise en garde 3 :OFFSET
Si OFFSET
est égal ou supérieur au nombre de lignes renvoyées par le CTE, vous obtenez aucune ligne , donc pas de décompte total non plus. Voir :
Solution provisoire
En tenant compte de toutes les mises en garde jusqu'à présent, et sur la base d'informations supplémentaires, nous pourrions arriver à cette requête :
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;
Maintenant, le CTE est en fait utilisé deux fois. Le RIGHT JOIN
garantit que nous obtenons le nombre total, quel que soit le OFFSET
. DISTINCT ON
devrait fonctionner correctement pour les quelques lignes seulement par (person_id)
dans la requête de base.
Mais vous avez de larges rangées. Quelle largeur en moyenne ? La requête entraînera probablement une analyse séquentielle sur l'ensemble de la table. Les index n'aideront pas (beaucoup). Tout cela restera extrêmement inefficace pour la pagination . Voir :
Vous ne pouvez pas impliquer un index pour la pagination car celui-ci est basé sur la table dérivée du CTE. Et vos critères de tri réels pour la pagination ne sont toujours pas clairs (ORDER BY id
?). Si la pagination est l'objectif, vous avez désespérément besoin d'un style de requête différent. Si vous n'êtes intéressé que par les premières pages, vous avez encore besoin d'un style de requête différent. La meilleure solution dépend des informations encore manquantes dans la question...
Rapidement plus rapide
Pour votre objectif mis à jour :
(Ignorer "pour les critères de filtre spécifiés, le type, le plan, le statut" pour plus de simplicité.)
Et :
Sur la base de ces deux indices spécialisés :
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);
Exécutez cette requête :
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;
Chaque jeu de parenthèses ici est obligatoire.
Ce niveau de sophistication devrait récupérer un ensemble relativement petit de lignes supérieures radicalement plus rapidement en utilisant les indices donnés et sans analyse séquentielle. Voir :
submission_date
devrait très probablement être de type timestamptz
ou date
, pas - qui est une définition de type étrange dans Postgres dans tous les cas. Voir :character varying(255)
Beaucoup plus de détails pourraient être optimisés, mais cela devient incontrôlable. Vous pourriez envisager de consulter un professionnel.