前回なにをやったのか、というと、率直にリンク先を見ていただければ良いと思う。
「SQLでちゃんとしたランキングを作る【1/3】」
つまりseletしてorderしてlimitするだけでは正しいランキングがでない、ということだ。
幸いにも識者の方から色々アドバイスをもらい、俺なりに試してみた結果を書いてみようと思う。
まず、識者、つまりDupont氏から引用してもらったSQLがこれだ。
select (select count(distinct b.sal) from emp b where b.sal <= a.sal) as rnk, a.sal from emp a個人的にSQLは改行を入れて見やすくする主義なので、以下のように適宜変更してみる。
select ( select count(distinct b.sal) from emp b where b.sal <= a.sal ) as rnk, a.sal from emp aコードというものは、改行やインデントで適切にフォーマットしておくべきだと思っている。
しかしSQLに関してはまだ拘りはないので、だいたいいつもフォーマットは違う。統一しておいたほうが良い場合と、統一しないでも良い場合があるのだが、SQLは俺にとってまだ後者の方だ。
で、実はこのSQLはサンプルコードそのままなので、当然データベース名もテーブル名もフィールド名も違う。
既存データベースは以下のような内容になっている。
このデータを生成するには以下のSQLでインサートしてほしい。
CREATE TABLE IF NOT EXISTS `ranking_history` ( `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '主キー', `user_id` int(5) NOT NULL COMMENT 'ユーザID', `point` int(8) NOT NULL COMMENT 'ポイント', `created` date NOT NULL COMMENT '作成日', PRIMARY KEY (`id`), KEY `user_id` (`user_id`,`point`,`created`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `ranking_history` (`id`, `user_id`, `point`, `created`) VALUES (1, 1, 42, '2010-06-02'), (2, 2, 26, '2010-06-02'), (3, 3, 31, '2010-06-02'), (4, 4, 26, '2010-06-02'), (5, 5, 49, '2010-06-02'), (6, 6, 12, '2010-06-02'), (7, 7, 66, '2010-06-02'), (8, 8, 31, '2010-06-02'), (9, 9, 41, '2010-06-02'), (10, 10, 49, '2010-06-02'), (11, 11, 55, '2010-06-02'), (12, 12, 19, '2010-06-02'), (13, 13, 8, '2010-06-02'), (14, 14, 17, '2010-06-02'), (15, 15, 21, '2010-06-02'), (16, 15, 51, '2010-06-02'), (17, 16, 17, '2010-06-02'), (18, 17, 21, '2010-06-02'), (19, 18, 9, '2010-06-02'), (20, 19, 26, '2010-06-02'), (21, 20, 11, '2010-06-02'), (22, 21, 21, '2010-06-02'), (23, 21, 48, '2010-06-02'), (24, 23, 30, '2010-06-02'), (25, 24, 21, '2010-06-02'), (26, 25, 20, '2010-06-02');SQL自体あまり詳しい方ではないのだが、俺なりに解釈して、既存データベースに沿った形に変更してみた。
select ( select count(distinct B.point) from ranking_history as B where B.point >= A.point ) as RANK, A.point from ranking_history as Aそして上記SQLを実行してみた結果が以下だ。
ん?なんだこれ?これは本当に俺が求めているデータなのか?一体全体どうなんだ?もしかして俺は大いなる勘違いをしてしまっているのではないか?
しかしそれはこの世の終わりではない。
とりあえずpointを昇順にしてみよう。
select ( select count(distinct B.point) from ranking_history as B where B.point >= A.point ) as RANK, A.point from ranking_history as A order by A.point descこのSQLの結果は以下のようになる。
うむ、どうにかまともになりつつある事がわかるとおもう。
RANKは昇順になっていて、pointもちゃんと降順に鳴っている。
しかしまだいろいろ足りない部分があるではないか。これではただ昇順データと降順データが建に並んでいるだけに過ぎず、何をもって正しいランキングなのかなぞ、誰も証明できないではないか。
しかしそれはこの世の終わりではない。
まずは重複データと思われる部分を細かく分けるために、user_Idフィールドも表示させてみよう。
select ( select count(distinct B.point) from ranking_history as B where B.point >= A.point ) as RANK, A.user_id, A.point from ranking_history as A order by A.point desc内側のselectでA.user_idを追加しただけだ。
このSQLの結果は以下のようになる。
これで、今まで完全に重複していた8位の31ポイントなど、user_idが入ることで、しっかりと意味を持ったデータになった。
この時点で少々安心感が増した。
しかしここで1点考える。
現状、存在するすべてのデータを使っているため、ランキングが18位まで存在することになっている。
これを10位までにしたい場合どうするのか。
このSQLで単純にlimit句を使ってしまうと、単純に上から10個目、つまり8位で終わってしまう。
select ( select count(distinct B.point) from ranking_history as B where B.point >= A.point ) as RANK, A.user_id, A.point from ranking_history as A order by A.point desc limit 10これの結果が、以下のようになってしまう。
たしかにlimit 10がしっかり働いていて大安心!
って、そうじゃない。そうではないんだ。
- 上から10個
- RANKが10以下
多くの人がそうなのかも知れないし、俺だけなのかも知れないが、安易にlimitで出来るじゃん!とか思っていると、とたんにこう言った次のステージへ進むドアが勝手に開いてしまい、吸い込まれるように先に行かざるをえないケースというのが発生する。
このタイミングでレガーディな抵抗をするか、イノベーティブに前進するか、だ。
俺は素直に従おうと思う。
まずはwhere句でRANKの範囲を指定してみる。
select ( select count(distinct B.point) from ranking_history as B where B.point >= A.point ) as RANK, A.user_id, A.point from ranking_history as A where A.RANK <= 10 order by A.point descこれを実行するとエラーが出る。
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'LIMIT 0, 30' at line 17'LIMIT 0, 30'というのはphpMyAdminが勝手に付けるので無視するが、意味はこうだ。
貴様のSQLには文法上の誤りがある。
使っているMySQLのバージョンにあったマニュアルを一読されたし!
だ。相変わらず無粋且つ無礼なエラーメッセージである。正直、辟易である。
すでにしてもう意味を訳してみようなどと思わなくなるレベルまで達してしまっている。
もう少し、なんていうかこう、CakePHPのエラーまでじゃなくても、せめてphp位のエラーはだして欲しいと切に思う。
多分だけど、A.RANKがまずいような気がする。そして次回はそれをテストする。
まだまだ先は遠そうだが、出来るまでやるのが主義なので、頑張ろうと思う。
もし失敗してもまたやり直せば良い。それがこの世の終わりではないのだから。
続く。
facebook
twitter
google+
fb share