121 votes

Pagination MySQL sans double requête ?

Je me demandais s'il existait un moyen d'obtenir le nombre de résultats d'une requête MySQL, tout en limitant les résultats.

La façon dont la pagination fonctionne (d'après ce que je comprends), je fais d'abord quelque chose comme

query = SELECT COUNT(*) FROM `table` WHERE `some_condition`

Après avoir obtenu le num_rows(query), j'ai le nombre de résultats. Mais ensuite, pour limiter réellement mes résultats, je dois faire une deuxième requête comme :

query2 = SELECT COUNT(*) FROM `table` WHERE `some_condition` LIMIT 0, 10

Ma question : Existe-t-il un moyen de récupérer le nombre total de résultats qui seraient donnés, ET de limiter les résultats retournés en une seule requête ? Ou un moyen plus efficace de le faire. Merci.

9 votes

Bien que vous n'auriez pas COUNT(*) dans la requête2

75voto

Derrick Points 1059

Je ne fais presque jamais deux requêtes.

Il suffit de renvoyer une ligne de plus que nécessaire, d'en afficher seulement 10 sur la page et, s'il y en a plus que ce qui est affiché, d'afficher un bouton "Suivant".

SELECT x, y, z FROM `table` WHERE `some_condition` LIMIT 0, 11

// iterate through and display 10 rows.

// if there were 11 rows, display a "Next" button.

Votre requête doit être présentée dans l'ordre suivant : la plus pertinente d'abord. Il est probable que la plupart des gens ne se soucient pas d'aller à la page 236 sur 412.

Lorsque vous effectuez une recherche sur Google et que vos résultats ne figurent pas sur la première page, vous allez probablement à la page 2, et non à la page 9.

45 votes

En fait, si je ne le trouve pas sur la première page d'une requête Google, je passe généralement à la page 9.

3 votes

@Phil J'ai déjà entendu ça mais pourquoi faire ça ?

5 votes

Un peu tard, mais voici mon raisonnement. Certaines recherches sont dominées par des fermes de liens optimisées pour les moteurs de recherche. Ainsi, les premières pages sont les différentes fermes qui se battent pour la position numéro 1, le résultat utile est probablement toujours associé à la requête, mais pas en haut.

71voto

staticsan Points 14435

Non, c'est ainsi que de nombreuses applications qui veulent paginer doivent le faire. C'est fiable et sans faille, même si la requête est effectuée deux fois. Mais vous pouvez mettre le compte en cache pendant quelques secondes et cela vous aidera beaucoup.

L'autre moyen est d'utiliser SQL_CALC_FOUND_ROWS et ensuite appeler SELECT FOUND_ROWS() . en dehors du fait que vous devez mettre le FOUND_ROWS() appeler après, il y a un problème avec cela : Il y a un bug dans MySQL que cela chatouille que cela affecte ORDER BY ce qui la rend beaucoup plus lente sur les grandes tables que l'approche naïve de deux requêtes.

2 votes

Ce n'est pas tout à fait une preuve de condition de course, cependant, à moins que vous fassiez les deux requêtes dans une transaction. Mais ce n'est généralement pas un problème.

0 votes

Par "fiable", j'entends que le langage SQL lui-même renverra toujours le résultat souhaité, et par "à l'épreuve des balles", j'entends qu'aucun bogue de MySQL n'entrave le langage SQL que vous pouvez utiliser. Contrairement à l'utilisation de SQL_CALC_FOUND_ROWS avec ORDER BY et LIMIT, selon le bogue que j'ai mentionné.

5 votes

Dans le cas de requêtes complexes, l'utilisation de SQL_CALC_FOUND_ROWS pour récupérer le compte dans la même requête sera presque toujours plus lente que de faire deux requêtes séparées. En effet, cela signifie que toutes les lignes devront être récupérées dans leur intégralité, quelle que soit la limite, puis seules celles spécifiées dans la clause LIMIT seront retournées. Voir aussi ma réponse qui contient des liens.

29voto

thomasrutter Points 42905

Une autre approche pour éviter les doubles requêtes consiste à récupérer toutes les lignes de la page actuelle en utilisant d'abord une clause LIMIT, puis à effectuer une deuxième requête COUNT(*) uniquement si le nombre maximum de lignes a été récupéré.

Dans de nombreuses applications, le résultat le plus probable est que tous les résultats tiennent sur une seule page et que la pagination soit l'exception plutôt que la norme. Dans ces cas, la première requête ne permettra pas de récupérer le nombre maximum de résultats.

Par exemple, les réponses à une question de stackoverflow débordent rarement sur une deuxième page. Les commentaires sur une réponse dépassent rarement la limite des 5 ou plus nécessaires pour les afficher tous.

Ainsi, dans ces applications, vous pouvez simplement effectuer une requête avec une LIMITE en premier lieu, et tant que cette limite n'est pas atteinte, vous savez exactement combien de lignes il y a sans avoir besoin d'effectuer une seconde requête COUNT(*) - ce qui devrait couvrir la majorité des situations.

1 votes

@thomasrutter J'avais la même approche, mais j'ai découvert une faille dans cette approche aujourd'hui. La dernière page de résultats n'aura pas les données de pagination. Par exemple, disons que chaque page devrait avoir 25 résultats, la dernière page n'en aura probablement pas autant, disons qu'elle en aura 7... cela signifie que le count(*) ne sera jamais exécuté, et donc qu'aucune pagination ne sera affichée à l'utilisateur.

2 votes

Non - si vous avez, disons, 200 résultats, que vous interrogez les 25 suivants et que vous n'en obtenez que 7, cela vous indique que le nombre total de résultats est de 207 et que vous n'avez donc pas besoin d'effectuer une autre requête avec COUNT(*) car vous savez déjà ce qu'elle va donner. Vous avez toutes les informations dont vous avez besoin pour afficher la pagination. Si vous avez un problème avec la pagination qui ne s'affiche pas pour l'utilisateur, alors vous avez un bug ailleurs.

16voto

thomasrutter Points 42905

Dans la plupart des situations, il est beaucoup plus rapide et moins gourmand en ressources de le faire en deux requêtes distinctes qu'en une seule, même si cela semble contre-intuitif.

Si vous utilisez SQL_CALC_FOUND_ROWS, alors pour les grandes tables, cela rend votre requête beaucoup plus lente, significativement plus lente même que l'exécution de deux requêtes, la première avec un COUNT(*) et la seconde avec un LIMIT. La raison en est que SQL_CALC_FOUND_ROWS entraîne l'application de la clause LIMIT après récupérer les lignes au lieu d'avant, de sorte qu'il récupère la ligne entière pour tous les résultats possibles avant d'appliquer les limites. Cela ne peut pas être satisfait par un index, car il récupère réellement les données.

Si vous adoptez l'approche des deux requêtes, la première se contentant de récupérer COUNT(*) et ne récupérant pas les données réelles, cela peut être satisfait beaucoup plus rapidement car elle peut généralement utiliser des index et n'a pas à récupérer les données réelles de chaque ligne qu'elle regarde. Ensuite, la deuxième requête n'a besoin de regarder que les premières lignes de $offset+$limit et de retourner.

Cet article du blog sur les performances de MySQL explique cela plus en détail :

http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Pour plus d'informations sur l'optimisation de la pagination, consultez le site suivant ce poste y ce poste .

2voto

Cris McLaughlin Points 620
query = SELECT col, col2, (SELECT COUNT(*) FROM `table`)/10 AS total FROM `table` WHERE `some_condition` LIMIT 0, 10

Où 10 est la taille de la page et 0 est le numéro de la page (vous devez utiliser pageNumber-1 dans la requête).

18 votes

Cette requête ne renvoie que le nombre total d'enregistrements dans la table, et non le nombre d'enregistrements qui correspondent à la condition.

1 votes

Le nombre total d'enregistrements est ce qui est nécessaire pour la pagination (@Lawrence).

0 votes

Oh, eh bien, il suffit d'ajouter le where à la requête interne et vous obtenez le bon "total" avec les résultats paginés (la page est sélectionnée avec la clause limit clause

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X