web-dev-qa-db-fra.com

Meilleur moyen d'obtenir le nombre de résultats avant d'appliquer LIMIT

Lorsque vous recherchez des données provenant d'une base de données, vous devez savoir combien de pages il y aura pour rendre les contrôles de saut de page.

Actuellement, je le fais en exécutant la requête deux fois, une fois enveloppée dans une count() pour déterminer le total des résultats, et une deuxième fois avec une limite appliquée pour récupérer uniquement les résultats dont j'ai besoin pour la page actuelle.

Cela semble inefficace. Existe-t-il un meilleur moyen de déterminer le nombre de résultats qui auraient été renvoyés avant l'application de LIMIT?

J'utilise PHP et Postgres.

56
EvilPuppetMaster

SQL pur

Les choses ont changé depuis 2008. Vous pouvez utiliser une fonction de fenêtre pour obtenir le nombre total et le résultat limité en une seule requête. (Introduit avec PostgreSQL 8.4 en 2009 ).

SELECT foo
     , count(*) OVER() AS full_count
FROM   bar
WHERE  <some condition>
ORDER  BY <some col>
LIMIT  <pagesize>
OFFSET <offset>

Notez que cela peut être considérablement plus cher que sans le nombre total. Toutes les lignes doivent être comptées, et un raccourci possible prenant uniquement les lignes supérieures d'un index correspondant peut ne plus être utile.
Peu importe les petites tables ou full_count <= OFFSET + LIMIT. C'est important pour un full_count Considérablement plus grand.

Cas d'angle: lorsque OFFSET est au moins aussi grand que le nombre de lignes de la base , pas de ligne est retourné. Vous n'avez donc pas non plus de full_count. Alternative possible:

Considérez la séquence d'événements :

  1. La clause WHERE (et les conditions JOIN, mais pas ici) filtrent les lignes éligibles des tables de base.

    (GROUP BY Et les fonctions d'agrégation iraient ici.)

  2. Les fonctions de fenêtre sont appliquées en tenant compte de toutes les lignes qualifiées (en fonction de la clause OVER et de la spécification du cadre de la fonction). La simple count(*) OVER() est basée sur toutes les lignes.

  3. ORDER BY

    (DISTINCT ou DISTINCT ON iraient ici.)

  4. LIMIT/OFFSET sont appliqués en fonction de l'ordre établi pour sélectionner les lignes à renvoyer.

LIMIT/OFFSET devient de plus en plus inefficace avec un nombre croissant de lignes dans la table. Envisagez des approches alternatives si vous avez besoin de meilleures performances:

Alternatives pour obtenir le décompte final

Il existe des approches complètement différentes pour obtenir le nombre de lignes affectées ( pas le nombre complet avant OFFSET & LIMIT ont été appliqués). Postgres a une comptabilité interne sur le nombre de lignes affectées par la dernière commande SQL. Certains clients peuvent accéder à ces informations ou compter les lignes eux-mêmes (comme psql).

Par exemple, vous pouvez récupérer le nombre de lignes affectées dans plpgsql immédiatement après l'exécution d'une commande SQL avec:

GET DIAGNOSTICS integer_var = ROW_COUNT;

Détails dans le manuel.

Ou vous pouvez utiliser pg_num_rows Dans [~ # ~] php [~ # ~] . Ou des fonctions similaires dans d'autres clients.

En relation:

118
Erwin Brandstetter

Comme je le décris sur mon blog , MySQL a une fonctionnalité appelée SQL_CALC_FOUND_ROWS . Cela supprime la nécessité d'exécuter la requête deux fois, mais il doit toujours exécuter la requête dans son intégralité, même si la clause limitative lui aurait permis de s'arrêter plus tôt.

Pour autant que je sache, il n'y a pas de fonctionnalité similaire pour PostgreSQL. Une chose à surveiller lors de la pagination (la chose la plus courante pour laquelle LIMIT est utilisé à mon humble avis): faire un "OFFSET 1000 LIMIT 10" signifie que la base de données doit récupérer au moins 1010 lignes, même si cela ne vous donne que 10. Une manière plus performante de le faire est de se souvenir de la valeur de la ligne que vous commandez pour la ligne précédente (la 1000e dans ce cas) et de réécrire le requête comme ceci: "... WHERE order_row> value_of_1000_th LIMIT 10". L'avantage est que "order_row" est très probablement indexé (sinon, vous avez un problème). L'inconvénient est que si de nouveaux éléments sont ajoutés entre les pages vues, cela peut être un peu désynchronisé (mais là encore, il peut ne pas être observable par les visiteurs et peut être un gros gain de performances).

5
Grey Panther

Vous pouvez atténuer la baisse des performances en n'exécutant pas la requête COUNT () à chaque fois. Mettez en cache le nombre de pages pendant, disons 5 minutes avant de relancer la requête. À moins que vous ne voyiez un grand nombre d'insertions, cela devrait très bien fonctionner.

1
Bob Somers

Puisque Postgres fait déjà une certaine quantité de mise en cache, ce type de méthode n'est pas aussi inefficace qu'il n'y paraît. Ce n'est certainement pas un doublement du temps d'exécution. Nous avons des minuteries intégrées dans notre couche DB, j'ai donc vu les preuves.

0
grantwparks