Сложный SQL-запрос:популярность тегов для моделей со сложными ассоциациями
Вопрос
Я даже не уверен, что это можно сделать эффективно, но вот моя проблема:
Я пишу то, что по сути является движком блога, где можно пометить сообщение в блоге и все ответы на каждое сообщение в блоге.
Итак, я мог бы разместить сообщение в блоге с пометкой «стек», а ответ на это сообщение — с пометкой «переполнение».
Прямо сейчас я пытаюсь создать список самых популярных тегов, когда пользователь попадает на специальную страницу в моем приложении.Он должен возвращать не только n самых популярных тегов в порядке убывания количества сообщений в блоге, но также количество сообщений в блоге, связанных с каждым тегом. даже если этим тегом помечен ответ в этом сообщении, но не само сообщение.
Таким образом, если BlogPost A помечен тегом «foo», а ответ в BlogPost B помечен тегом «foo», в сводке популярных тегов это должно считаться в общей сложности двумя сообщениями в блоге, даже если BlogPost B технически не помечен тегом.
Вот описание таблиц/полей, которые могут иметь значение:
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
Для удобства в тегах есть некоторая денормализация.Если кто-то помечает BlogPost, поле blog_post_id заполняется, а blog_comment_id остается NULL.Если кто-то помечает комментарий к сообщению, он заполняется как blog_post_id, так и blog_comment_id.
Есть ли способ вернуть отсортированный список самых популярных тегов в одном или нескольких запросах SQL?Я думаю, мне, возможно, придется просто запускать ресурсоемкий с точки зрения вычислений сценарий каждые несколько минут в задании cron и отображать кэшированные выходные данные вместо того, чтобы запускать его каждый раз, когда кто-то заходит на страницу...
Спасибо!
Решение
Пока ничего сложного в вашем запросе не вижу:
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
Если вы хотите учитывать только «затронутые сообщения в блоге», я думаю, что это так:
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
Другие советы
Возможно, я упускаю что-то очевидное, но поскольку у вас есть «Если кто-то помечает комментарий к сообщению, он заполняет как blog_post_id, так и blog_comment_id», следующий sql должен помочь. Я предполагаю, что здесь Tags.name будет уникальным.
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
Надеюсь, это то, что вы ищете.
Я не пробовал, а как насчет такого?:
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