ランキングコンテンツ作成時のテーブル設計や個別の順位取得する時の参考


年に数回の頻度であるが、ランキングのコンテンツはある程度やっております。
投票テーブルから(下記でいうところのvote)毎回sumして結果を出すのも
なんだかなと思ったので、今回はカウントアップしつつ、ログも残すという方法を試してみた。

以下、簡単に説明を記述する。
実装はcakephp2.xを使ったが、別に言語やフレームワークは関係なく
ランキングの実装法として参考にしてもらえれば幸いである。
(ポイントやお金が絡む場合はもう少し実装を深く考える必要があるので、そのケースは除いて欲しい)

まず、前提として
・特に投票に制限はない(制限があっとしてもアプリ側でcookie 使うなりなんなりしてください)
・投票はその商品に対して投票ボタンみたいなものを押せば投票完了とする。
ものとする。

まずテーブル構成から、トリガやビューとかは使わないものとして

table : products
id | name | count
------------------
1  | a    | 0
2  | b    | 0
3  | c    | 0
4  | d    | 0
(略)

みたいなテーブルを用意し、これとは別で投票テーブルを持つ(なんだかんだ履歴残しておいた方が安心だから)

table : vote
id | product_id | createdate
--------------------------------------
1  | 1      | 2016/12/1 00:00:00
2  | 2      | 2016/12/1 00:00:00
3  | 3      | 2016/12/1 00:00:00
4  | 5      | 2016/12/1 00:00:00
(略)

それで投票ボタンが押された時に、voteテーブルへInsertしてかつ
productのテーブルの該当商品のcountを一個づつ、プラスしていく。

この方式であれば、個別の順位が知りたい時は

SELECT (SELECT COUNT(*) FROM products AS b WHERE a.count < b.count) + 1 AS rank FROM products AS a WHERE id = ".$id." ORDER BY a.count DESC";

を実行すれば個別の順位が取得できる。

一点注意が必要で、上記SQLは、同列順に対応しているので、ランキング一覧を出す時は

SELECT * FROM products ORDER BY count DESC;

などしてcountが多い順でデータを取得したのちに、個別にループさせて

SELECT (SELECT COUNT(*) FROM products AS b WHERE a.count < b.count) + 1 AS rank FROM products AS a WHERE id = ".$id." ORDER BY a.count DESC";

を実行し、結果を配列等に入れてviewへ渡す、必要がある。

表示件数にもよるが、1ページあたりの表示数がしれている場合はあんまり重たくなりにくいだろうし
これぐらいの実装で良いのではないかと思う。