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

90

À des fins de pagination, j'ai besoin d'exécuter une requête avec les clauses LIMITet 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 LIMITand OFFSET.

Je veux courir:

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

Et:

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

En même temps. Y a-t-il un moyen de le faire, en particulier un moyen qui permet à Postgres de l'optimiser, de sorte que ce soit plus rapide que d'exécuter les deux individuellement?

Tim
la source
2
Est-ce que cela répond à votre question? Meilleur moyen d'obtenir le nombre de résultats avant l'application de LIMIT
Marty Neal

Réponses:

168

Oui. Avec une simple fonction de fenêtre:

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

Sachez que le coût sera nettement plus élevé que sans le nombre total, mais généralement encore moins cher que deux requêtes distinctes. Postgres doit en fait compter toutes les lignes de toute façon, ce qui impose un coût dépendant du nombre total de lignes éligibles. Détails:

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

Si ce n'est pas acceptable, une solution de contournement possible pour toujours renvoyer 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_countsi ajouté OFFSETest trop grand. Sinon, 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_countpour lever l'ambiguïté de l'origine de la ligne vide.

Cela n'exécute toujours la requête de base qu'une seule fois. Mais cela ajoute plus de surcharge à 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 payant d'inclure le ORDER BYdans le CTE (de manière redondante).

Erwin Brandstetter
la source
3
À la fois par LIMIT et par conditions, nous avons des lignes à renvoyer, mais avec le décalage donné, cela ne renverrait aucun résultat. Dans cette situation, comment pourrions-nous obtenir le nombre de lignes?
Dani Mathew
très bien, merci, fonctionne très bien lorsque vous utilisez la pagination, les tables de données, ajoutez simplement ceci au début de votre SQL, et utilisez-le, enregistrez une requête supplémentaire pour le nombre total.
Ahmed Sunny
Pourriez-vous nous en dire plus si le comptage pouvait être activé dynamiquement dans la requête via un paramètre d'entrée? J'ai une exigence similaire, mais l'utilisateur décide s'il veut ou non le nombre en ligne.
julealgon
1
@julealgon: Veuillez commencer une nouvelle question avec les détails déterminants. Vous pouvez toujours créer un lien vers celui-ci pour le contexte et ajouter un commentaire ici pour renvoyer (et attirer mon attention) si vous le souhaitez.
Erwin Brandstetter
1
@JustinL .: La surcharge ajoutée ne devrait être significative que pour les requêtes de base relativement bon marché. En outre, Postgres 12 a amélioré les performances du CTE de plusieurs manières. (Bien que ce CTE soit toujours MATERIALIZEDpar défaut, étant référencé deux fois.)
Erwin Brandstetter
1

edit: cette réponse est valide lors de la récupération de la table non filtrée. Je vais le laisser au cas où cela pourrait aider quelqu'un, mais cela pourrait ne pas répondre exactement à la question initiale.

La réponse d' Erwin Brandstetter est parfaite si vous avez besoin d'une valeur précise. Cependant, sur de grandes tables, vous n'avez souvent besoin que d'une assez bonne approximation. Postgres vous donne exactement cela et ce sera beaucoup plus rapide car il n'aura pas besoin d'évaluer chaque ligne:

SELECT *
FROM (
    SELECT *
    FROM tbl
    WHERE /* something */
    ORDER BY /* something */
    OFFSET ?
    LIMIT ?
    ) data
RIGHT JOIN (SELECT reltuples FROM pg_class WHERE relname = 'tbl') pg_count(total_count) ON true;

En fait, je ne suis pas sûr qu'il y ait un avantage à externaliser le RIGHT JOINou à l'avoir comme dans une requête standard. Cela mériterait des tests.

SELECT t.*, pgc.reltuples AS total_count
FROM tbl as t
RIGHT JOIN pg_class pgc ON pgc.relname = 'tbl'
WHERE /* something */
ORDER BY /* something */
OFFSET ?
LIMIT ?
François Gueguen
la source
2
À propos de l'estimation rapide du nombre: stackoverflow.com/a/7945274/939860 Comme vous l'avez dit: valide lors de la récupération de la table entière - ce qui est contredit par la WHEREclause dans vos requêtes. La deuxième requête est logiquement erronée (récupère une ligne pour chaque table de la base de données) - et plus chère lorsqu'elle est corrigée.
Erwin Brandstetter
-7

C'est une mauvaise pratique d'appeler deux fois la même requête pour Just pour obtenir le nombre total de lignes du résultat de retour. Cela prendra du temps d'exécution et gaspillera la ressource serveur.

Mieux, vous pouvez utiliser SQL_CALC_FOUND_ROWSdans la requête qui indiquera à MySQL de récupérer le nombre total de lignes avec les résultats de la requête de limite.

Exemple défini comme:

SELECT SQL_CALC_FOUND_ROWS employeeName, phoneNumber FROM employee WHERE employeeName LIKE 'a%' LIMIT 10;

SELECT FOUND_ROWS();

Dans la requête ci-dessus, ajoutez simplement l' SQL_CALC_FOUND_ROWSoption dans le reste de la requête requise et exécutez la deuxième ligne, c'est-à-dire SELECT FOUND_ROWS()renvoie le nombre de lignes dans le jeu de résultats renvoyé par cette instruction.

Mohd Rashid
la source
1
La solution nécessite postgres, pas mysql.
MuffinMan
@MuffinMan, vous pouvez utiliser la même chose sur mysql. Depuis MYSQL 4.0, il est utilisé l'option SQL_CALC_FOUND_ROWS dans la requête. Mais à partir de MYSQL 8.0, il est déprécié.
Mohd Rashid
Non pertinent. Cette question a reçu une réponse il y a des années. Si vous souhaitez contribuer, postez une nouvelle question avec le même sujet mais spécifique à MySQL.
MuffinMan
soyez toujours pertinent
Ali Hussain
-14

Non.

Il y a peut-être un petit gain que vous pourriez théoriquement gagner en les exécutant individuellement avec suffisamment de machines compliquées sous le capot. Mais, si vous voulez savoir combien de lignes correspondent à une condition, vous devrez les compter plutôt qu'un sous-ensemble limité.

Richard Huxton
la source