tag:crieit.net,2005:https://crieit.net/users/aequor_6000m/feed sinkの投稿 - Crieit Crieitでユーザーsinkによる最近の投稿 2019-12-09T10:28:21+09:00 https://crieit.net/users/aequor_6000m/feed tag:crieit.net,2005:PublicArticle/15593 2019-12-08T18:59:22+09:00 2019-12-09T10:28:21+09:00 https://crieit.net/posts/PostgreSQL PostgreSQL: とにかく簡単にトレンドやホットランキングを出したい <p>CGMなどを作っていると、現在人気の投稿をトレンドとしてランキング表示したいときがある<br /> これをSQLだけで実装する<br /> パフォーマンスも何も考えていないしテーブル構成にも依存しているし、さらには手動で調整するマジックナンバーさえある<br /> だが、簡単なのでとりあえずそれっぽい機能を付けたいとなったときに役立つかもしれない</p> <h1 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h1> <p>PostgreSQL</p> <h1 id="テーブル構成1"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E6%A7%8B%E6%88%901">テーブル構成1</a></h1> <p>ランク付けの対象として投稿を表すPOSTテーブルがあり、閲覧数を表すVIEWテーブルと一対多のリレーションになっているとする(閲覧されるたびにVIEWテーブルのレコードが増えるパターン)</p> <p>また、各テーブルには作成日時created_atと更新日時updated_atがあるとする</p> <p><a href="https://crieit.now.sh/upload_images/eb23ee475f9ebc4e2f26928c82bdba9a5decb796a57a9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/eb23ee475f9ebc4e2f26928c82bdba9a5decb796a57a9.png?mw=700" alt="Entity Relationship Diagram.png" /></a></p> <h1 id="SQL"><a href="#SQL">SQL</a></h1> <pre><code class="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; </code></pre> <h2 id="説明"><a href="#%E8%AA%AC%E6%98%8E">説明</a></h2> <p>LEFT OUTER JOINで結合する時にVIEWテーブルの条件を調整している<br /> <code>"view"."created_at" >= now() - interval '1 hour'</code>は現在時刻から1時間前までの間に作成されたVIEWのレコードのみ結合するという条件で、これをカウントしてrank_pointとし、post.idでグルーピングしたものを降順に並べれば1時間のうちで閲覧が多い投稿が順に並ぶはずである<br /> <code>interval '1 hour'</code>の部分は好きなように調整できる</p> <h1 id="テーブル構成2"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E6%A7%8B%E6%88%902">テーブル構成2</a></h1> <p>他の指標を使いたくなったらいくらでも追加できる<br /> 例えば投稿には一対多でコメントがあって、コメントの多さでも人気なことを表したいとする<br /> <a href="https://crieit.now.sh/upload_images/eb23ee475f9ebc4e2f26928c82bdba9a5decc0f9a666b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/eb23ee475f9ebc4e2f26928c82bdba9a5decc0f9a666b.png?mw=700" alt="Entity Relationship Diagram.png" /></a></p> <h1 id="SQL"><a href="#SQL">SQL</a></h1> <pre><code class="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; </code></pre> <h2 id="説明"><a href="#%E8%AA%AC%E6%98%8E">説明</a></h2> <p>VIEWに加えてCOMMENTも同じように結合する<br /> あとは適当に掛け算して桁を合わせてから指標の数で割ったものをrank_pointとするだけだ<br /> 桁を合わせるとは書いたが、重視したい指標に多めの数値を掛けるなりすれば重み付けもできる</p> <h1 id="蛇足"><a href="#%E8%9B%87%E8%B6%B3">蛇足</a></h1> <p>最初はちゃんと移動平均を出そうと思ってWindow関数とか調べたが、複数テーブルが絡むと面倒な記述になりそうでやめてしまった</p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://www.postgresql.jp/document/11/html/functions-datetime.html">9.9. 日付/時刻関数と演算子</a></p> sink