web-dev-qa-db-fra.com

Exécutez une requête avec LIMIT / OFFSET et obtenez également le nombre total de lignes

À des fins de pagination, j'ai besoin d'exécuter une requête avec les clauses LIMIT et OFFSET. Mais j'ai également besoin d'un décompte du nombre de lignes qui seraient renvoyées par cette requête sans les clauses LIMIT et OFFSET.

Je veux courir:

SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?

Et:

SELECT COUNT(*) FROM table WHERE /* whatever */

À la fois. Existe-t-il un moyen de le faire, en particulier un moyen qui permet à Postgres de l'optimiser, de sorte qu'il soit plus rapide que d'exécuter les deux individuellement?

51
Tim

Oui. Avec une simple fonction de fenêtre:

SELECT *, count(*) OVER() AS full_count
FROM   tbl
WHERE  /* whatever */
ORDER  BY col1
LIMIT  ?
OFFSET ?

Sachez que le coût sera considérablement plus élevé que sans le nombre total, mais toujours moins cher que deux requêtes distinctes. Postgres doit en fait compter toutes les lignes dans les deux cas, ce qui impose un coût en fonction du nombre total de lignes éligibles. Détails:

Cependant , comme Dani l'a souligné , lorsque OFFSET est au moins aussi grand que le nombre de lignes renvoyées à partir de la requête de base, aucune ligne n'est renvoyée. Nous n'obtenons donc pas non plus le full_count.

Si ce n'est pas acceptable, une solution de contournement possible qui renvoie toujours le nombre complet serait avec un CTE et un OUTER JOIN:

WITH cte AS (
   SELECT *
   FROM   tbl
   WHERE  /* whatever */
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY col1
   LIMIT  ?
   OFFSET ?
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(full_count) ON true;

Vous obtenez une ligne de valeurs NULL avec le full_count ajouté si OFFSET est trop grand. Ou il est ajouté à chaque ligne comme dans la première requête.

Si une ligne avec toutes les valeurs NULL est un résultat valide possible, vous devez vérifier offset >= full_count pour lever l'ambiguïté de l'origine de la ligne vide.

Cela exécute toujours la requête de base une seule fois. Mais cela ajoute plus de frais généraux à la requête et ne paie que si c'est moins que de répéter la requête de base pour le nombre.

Si des index prenant en charge l'ordre de tri final sont disponibles, il peut être utile d'inclure le ORDER BY dans le CTE (redondant).

100