Una query SQL difficile: popolarità dei tag per i modelli con associazioni complesse
Domanda
Non sono nemmeno sicuro che sia possibile farlo in modo efficiente, ma ecco il mio problema:
Sto scrivendo quello che è essenzialmente un motore di blog in cui un post di blog e tutte le risposte a ciascun post di blog possono essere taggati.
Quindi, potrei avere un post sul blog taggato " stack " ;, e una risposta a quel post taggato " overflow " ;.
In questo momento, sto cercando di generare un elenco dei tag più popolari quando un utente accede a una pagina speciale nella mia applicazione. Dovrebbe restituire non solo i n tag più popolari in base al numero decrescente di post di blog, ma anche il numero di post di blog associati a ciascun tag, anche se una risposta in quel post ma non il post stesso è taggata con quel tag .
Quindi, se BlogPost A è taggato con " foo " ;, e una risposta in BlogPost B è taggata con " foo " ;, il popolare sommario dei tag dovrebbe considerarlo come due post di blog in totale, anche se BlogPost B non lo è tecnicamente taggato.
Ecco una descrizione delle tabelle / campi che potrebbero essere rilevanti:
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
C'è qualche denormalizzazione in Taggings per motivi di convenienza. Se qualcuno tagga BlogPost, riempie il campo blog_post_id e blog_comment_id rimane NULL. Se qualcuno tagga un commento a un post, riempie sia blog_post_id che blog_comment_id.
Esiste un modo per restituire un elenco ordinato dei tag più popolari in una o più query SQL? Sto pensando che potrei aver bisogno di eseguire uno script costoso dal punto di vista computazionale ogni pochi minuti su un processo cron e rendere l'output memorizzato nella cache invece di eseguirlo ogni volta che qualcuno accede alla pagina ...
Grazie!
Soluzione
Finora non vedo nulla di complicato nella tua richiesta:
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
Se desideri contare " post di blog interessati " solo, penso che sia così:
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
Altri suggerimenti
Forse mi manca qualcosa di ovvio, ma poiché hai " Se qualcuno tagga un commento ad un post, riempie sia blog_post_id che blog_comment_id " ;, il seguente sql dovrebbe fare il trucco. Suppongo che Tag.name qui sia unico.
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
Spero che sia quello che stai cercando.
Non ho provato, ma che dire di qualcosa del genere ?:
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