2009-03-06 9 views
1

Je ne suis même pas sûr que ce soit possible de le faire efficacement, mais voici mon problème:Une requête SQL difficile: popularité tag pour les modèles avec des associations complexes

J'écris ce qui est essentiellement un moteur de blog où un billet de blog et toutes les réponses à chaque article de blog peuvent être marquées. Donc, je pourrais avoir un article de blog étiqueté "pile", et une réponse à ce poste marqué "débordement".

À l'heure actuelle, j'essaie de générer une liste des balises les plus populaires lorsqu'un utilisateur clique sur une page spéciale de mon application. Il convient de renvoyer non seulement les tags les plus populaires par nombre décroissant de blogs, mais également le nombre de billets associés à chaque tag, même si une réponse dans ce post mais pas le post lui-même est balisée avec cette balise. Par conséquent, si BlogPost A est marqué avec "foo" et qu'une réponse dans BlogPost B est associée à "foo", le récapitulatif de la balise doit compter au total deux articles de blog, même si BlogPost B ne l'est pas techniquement. étiqueté.

Voici une description des tables/champs qui pourraient être pertinents:

BlogPosts 
| id  # Primary key for all tables, Rails-style 

BlogComments 
| id 
| blog_post_id 

Tags 
| id 
| name # 'foo' 

Taggings 
| id 
| tag_id 
| blog_post_id 
| blog_comment_id 

Il y a quelques dénormalisation pour le bien des marquages ​​de commodité. Si quelqu'un balise BlogPost, il remplit le champ blog_post_id et blog_comment_id reste NULL. Si quelqu'un marque un commentaire dans un post, il remplit à la fois blog_post_id et blog_comment_id.

Existe-t-il un moyen de renvoyer une liste triée des balises les plus populaires dans une ou plusieurs requêtes SQL? Je pense que je devrais juste exécuter un script calcul-coûteuse toutes les quelques minutes sur un travail de cron et rendre la sortie mise en cache au lieu d'exécuter ceci chaque fois que quelqu'un frappe la page ...

Merci!

Répondre

1

Jusqu'à présent, je ne vois rien de compliqué dans votre demande:

SELECT 
    tag_id, 
    COUNT(blog_post_id) + COUNT(blog_comment_id) tag_count 
FROM 
    Taggings 
GROUP BY 
    tag_id 
ORDER BY 
    COUNT(blog_post_id) + COUNT(blog_comment_id) DESC 

Si vous voulez compter « messages de blog affectés » uniquement, je pense que c'est la façon:

SELECT 
    t.id tag_id, 
    t.name tag_name, 
    COUNT(DISTINCT COALESCE(x.blog_post_id, c.blog_post_id)) tag_count 
FROM 
    Tags     t 
    INNER JOIN Taggings  x ON x.tag_id = t.id 
    LEFT JOIN BlogComments c ON c.id  = x.blog_comment_id 
GROUP BY 
    t.id, 
    t.name 
ORDER BY 
    COUNT(DISTINCT COALESCE(x.blog_post_id, c.blog_post_id)) DESC 
+0

qui retourne un compte d'étiquette de 2 quand je baliser un commentaire de blog, depuis COUNT (blog_post_id) est 1 et COUNT (blog_comment_id) est 1. –

+0

Huh - mais je pense que si je sors les parties "+ COUNT (blog_comment_id)" ça me donne exactement ce que je voulais. Cool! Je suppose que ce n'était pas si mal du tout. Merci. –

+0

Je pensais que vous vouliez compter les commentaires et les messages? Je peux vous avoir mal compris. – Tomalak

0

je manquerai Quelque chose d'évident mais puisque vous avez "Si quelqu'un balise un commentaire à un post, il remplit à la fois blog_post_id et blog_comment_id", le sql suivant devrait faire l'affaire. Je suppose ici que Tags.name ici sera unique.

SELECT MIN(ts.tag_id), t.name, COUNT(ts.blog_post_id) as rank 
FROM Taggings ts 
    INNER JOIN Tags t ON ts.tag_id = t.id 
GROUP BY t.name 
ORDER BY COUNT(ts.blog_post_id) DESC 

espoir que est ce que vous cherchez.

0

Je ne l'ai pas essayé, mais ce quelque chose comme ça ?:

select t.Id, 
    t.Name, 
    count(*) 

from Taggings tings 
inner join Tags t 
    on (t.id = tings.blog_post_id or t.id = tings.blog_comment_id) 

group by t.Id, t.Name 
order by count(*) desc