2008-12-06 15 views
2

Sur mon blog, je souhaite afficher tous les articles du mois dernier. Mais si ce nombre est inférieur à 10, je veux afficher les dix articles les plus récents (en d'autres termes, il ne devrait jamais y avoir moins de 10 articles sur la première page). Je me demande s'il existe un moyen de le faire dans une seule requête?Comment faire une requête pour les 10 articles les plus récents ou les articles du mois dernier, selon ce qui est le plus?

Actuellement, j'exécutons d'abord cette requête:

select count(*) from posts where timestamp > ($thirty_days_ago) 
order by timestamp desc 

Si ce nombre est supérieur ou égal à 10:

select * from posts where timestamp > ($thirty_days_ago) 
order by timestamp desc 

Sinon:

select * from posts order by timestamp desc limit 10 

Mais cela me oblige pour exécuter deux requêtes. Existe-t-il un moyen plus efficace de le faire avec une seule requête? (J'utilise MySQL.)

Répondre

2

Il suffit de faire ceci:

select * from posts order by timestamp desc limit 100 

et affinez davantage les résultats en mémoire. (Suppose que 100 est une limite supérieure pratique pour "messages dans un mois" que les gens voudraient voir dans une seule page)

Ceci est une "requête unique plus efficace".

+0

Mais j'imagine que votre hypothèse de 100 était la raison pour laquelle personne n'a suggéré cette stratégie. – dkretz

+1

Parfois, la correction perd. L'interface utilisateur est un endroit où cela se produit plus souvent que d'autres endroits que j'ai codés. –

+0

En fait, j'aime vraiment cette solution, sauf dans mon cas 30 serait une limite supérieure plus que suffisante (je ne prévois moi-même jamais en moyenne plus d'un post par jour). – Kip

5
(SELECT * FROM posts 
WHERE `timestamp` >= NOW() - INTERVAL 30 DAY) 
UNION 
(SELECT * FROM posts 
ORDER BY `timestamp` DESC 
LIMIT 10); 

modifier: Re @ Commentaire de doofledorfer: J'ai couru ceci sur ma base de données de test, et il a bien fonctionné. J'ai essayé de comparer timestamp à un littéral de date ainsi que l'expression constante comme indiqué dans la requête ci-dessus, mais cela n'a fait aucune différence dans le plan d'optimisation. Bien sûr, j'utilisais une quantité triviale de données, et le plan d'optimisation peut être différent s'il y a des milliers de lignes.

Dans tous les cas, l'OP demandait comment obtenir le résultat correct dans une seule requête, pas comment rendre le plan d'exécution optimal. C'est une requête UNION après tout, et est lié à engager un fichier.

+------+--------------+------------+------+---------------+------+---------+------+------+----------------+ 
| id | select_type | table  | type | possible_keys | key | key_len | ref | rows | Extra   | 
+------+--------------+------------+------+---------------+------+---------+------+------+----------------+ 
| 1 | PRIMARY  | posts  | ALL | timestamp  | NULL | NULL | NULL | 20 | Using where | 
| 2 | UNION  | posts  | ALL | NULL   | NULL | NULL | NULL | 20 | Using filesort | 
| NULL | UNION RESULT | <union1,2> | ALL | NULL   | NULL | NULL | NULL | NULL |    | 
+------+--------------+------------+------+---------------+------+---------+------+------+----------------+ 
+0

Ceci est toujours deux requêtes. –

+0

C'est une requête. Une opération UNION est similaire à une condition OR dans la clause WHERE –

+0

MAIS elle ne doit pas inclure une expression dans la première sous-requête. Et votre question est en effet deux requêtes, de sorte qu'il doit être exprimé comme tel d'une manière ou d'une autre. – dkretz

1

La seule façon que je peux le voir travailler avec une seule requête est de faire « select * from messages par ordre timestamp » qui retourne tous les messages, puis gérer la logique d'affichage dans votre code. Cependant, ce n'est pas une solution très efficace.

Tant que votre table est indexée correctement, l'exécution d'un comptage de sélection (*) suivi d'une requête de récupération ne devrait pas affecter les performances. Y a-t-il des circonstances particulières qui vous feraient spécifiquement essayer d'éviter une deuxième requête? Sinon, je pense que votre solution ci-dessus est suffisante.

1

Non, il n'y a pas de manière plus efficace. Je ferais comme vous le décrivez dans votre question. La réponse de Bill Karwin est à peu près équivalente si le prédicat est révisé comme je l'ai commenté plus haut.

Toutes les autres suggestions que j'ai vues jusqu'ici sont beaucoup moins efficaces, même si elles retournent le bon résultat.

0

Je pense que vous pouvez essayer quelque chose comme:

select * from posts 
where (timestamp >= (NOW() - INTERVAL 30 DAY)) or 
(post_id in (select post_id from posts order by timestamp desc limit 10)) 
order by timestamp desc 
+1

Prédicat horriblement inefficace et non-optimisable. – dkretz

-1

Idea1: faire une requête pour aller chercher toujours les messages de ce mois-ci. Ensuite, faites un cycle, en comptant le nombre de messages récupérés. Si, et seulement si, ce nombre est inférieur à 10, faites la deuxième requête.

Idée 2: Pourquoi ne pas cache votre première requête (Google App Engine, par exemple, a API Mise en cache)? Le nombre de messages pour ce mois est peu susceptible de changer souvent, vous supprimerez donc le besoin de la première requête dans la plupart des cas.

1

Recherchez-vous un seul balayage de table (par exemple un SELECT)? Ou un seul aller-retour au serveur de base de données? La réponse de Bill a un seul aller-retour mais deux SELECTs ... donc si cela constitue une ou deux "requêtes" dépend de ce que vous cherchez réellement quand vous dites "requête".

Si votre latence vers la base de données est très élevée, quelque chose comme la solution de Bill est la meilleure, car vous n'attendez pas beaucoup de communication. Si la base de données elle-même est chargé des analyses et des tables sont chers, votre mise en œuvre originale peut être mieux pour deux raisons:

  • Vous pouvez mettre en cache le résultat COUNT, de sorte qu'il ne sera exécuté une fois toutes les 10 minutes. Maintenant, vous avez effectivement amorti le coût de cette requête (si 200 visiteurs ont atteint cette page en 10 minutes, vous n'avez émis que 201 instructions SELECT).
  • Un moteur de base de données peut optimiser la requête COUNT pour atteindre un index au lieu de la table complète, ce qui le rend beaucoup plus rapide que d'essayer de UNION plusieurs ensembles de données ensemble. Je ne suis pas sûr que MySQL soit assez sophistiqué pour le faire ou non.
Questions connexes