2019-12-09に更新

PostgreSQL: とにかく簡単にトレンドやホットランキングを出したい

CGMなどを作っていると、現在人気の投稿をトレンドとしてランキング表示したいときがある
これをSQLだけで実装する
パフォーマンスも何も考えていないしテーブル構成にも依存しているし、さらには手動で調整するマジックナンバーさえある
だが、簡単なのでとりあえずそれっぽい機能を付けたいとなったときに役立つかもしれない

環境

PostgreSQL

テーブル構成1

ランク付けの対象として投稿を表すPOSTテーブルがあり、閲覧数を表すVIEWテーブルと一対多のリレーションになっているとする(閲覧されるたびにVIEWテーブルのレコードが増えるパターン)

また、各テーブルには作成日時created_atと更新日時updated_atがあるとする

Entity Relationship Diagram.png

SQL

SELECT "post".*, (COUNT(DISTINCT view))  as rank_point
FROM "post"
LEFT OUTER JOIN "view" ON "view"."post_id" = "post"."id" AND "view"."created_at" >= now() - interval '1 hour'
GROUP BY post.id
ORDER BY rank_point DESC;

説明

LEFT OUTER JOINで結合する時にVIEWテーブルの条件を調整している
"view"."created_at" >= now() - interval '1 hour'は現在時刻から1時間前までの間に作成されたVIEWのレコードのみ結合するという条件で、これをカウントしてrank_pointとし、post.idでグルーピングしたものを降順に並べれば1時間のうちで閲覧が多い投稿が順に並ぶはずである
interval '1 hour'の部分は好きなように調整できる

テーブル構成2

他の指標を使いたくなったらいくらでも追加できる
例えば投稿には一対多でコメントがあって、コメントの多さでも人気なことを表したいとする
Entity Relationship Diagram.png

SQL

SELECT "post".*, ( (COUNT(DISTINCT view) * 10) + (COUNT(DISTINCT comment) * 1000 ) ) / 2  as rank_point
FROM "post"
LEFT OUTER JOIN "view" ON "view"."post_id" = "post"."id" AND "view"."created_at" >= now() - interval '1 hour'
LEFT OUTER JOIN "comment" ON "comment"."post_id" = "post"."id" AND "comment"."created_at" >= now() - interval '1 hour'
GROUP BY post.id
ORDER BY rank_point DESC;

説明

VIEWに加えてCOMMENTも同じように結合する
あとは適当に掛け算して桁を合わせてから指標の数で割ったものをrank_pointとするだけだ
桁を合わせるとは書いたが、重視したい指標に多めの数値を掛けるなりすれば重み付けもできる

蛇足

最初はちゃんと移動平均を出そうと思ってWindow関数とか調べたが、複数テーブルが絡むと面倒な記述になりそうでやめてしまった

参考

9.9. 日付/時刻関数と演算子

ツイッターでシェア
みんなに共有、忘れないようにメモ

sink

サービスを作る。自分のサービスで食べていくのが目標。Vue.js/Rails/Kotlin

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント