À 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
and 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?
Réponses:
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
OFFSET
est 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 plusfull_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_count
si ajoutéOFFSET
est 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_count
pour 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 BY
dans le CTE (de manière redondante).la source
MATERIALIZED
par défaut, étant référencé deux fois.)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 JOIN
ou à 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 ?
la source
WHERE
clause 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.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_ROWS
dans 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_ROWS
option dans le reste de la requête requise et exécutez la deuxième ligne, c'est-à-direSELECT FOUND_ROWS()
renvoie le nombre de lignes dans le jeu de résultats renvoyé par cette instruction.la source
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é.
la source