ハイスコアを保存する非常にシンプルなMySQLテーブルがあります。それはそのように見えます:
Id Name Score
ここまでは順調ですね。問題は、ユーザーのランクを取得するにはどうすればよいですか?たとえば、ユーザーにName
またはId
があり、そのランクを取得したい場合、すべての行がScore
の降順で序数で並べられます。
例
Id Name Score
1 Ida 100
2 Boo 58
3 Lala 88
4 Bash 102
5 Assem 99
この場合、Assem
は3番目に高いスコアを獲得したため、ランクは3になります。
クエリは、必要なランク(のみ)を含む1行を返す必要があります。
SELECT id, name, score, FIND_IN_SET( score, (
SELECT GROUP_CONCAT( score
ORDER BY score DESC )
FROM scores )
) AS rank
FROM scores
このリストを与える:
id name score rank
1 Ida 100 2
2 Boo 58 5
3 Lala 88 4
4 Bash 102 1
5 Assem 99 3
一人のスコアを取得する:
SELECT id, name, score, FIND_IN_SET( score, (
SELECT GROUP_CONCAT( score
ORDER BY score DESC )
FROM scores )
) AS rank
FROM scores
WHERE name = 'Assem'
この結果を与えます:
id name score rank
5 Assem 99 3
スコアリストを取得するためのスキャンが1つあり、それを使用して別のスキャンを実行するか、有用な処理を実行します。 score
列のインデックスは、大きなテーブルでのパフォーマンスに役立ちます。
複数のエントリが同じスコアを持っている場合、次のランクは連続していてはなりません。次のランクは、同じランクを共有するスコアの数だけ増加する必要があります。
このようなスコアを表示するには、2つのランク変数が必要です
これは、同順位のランキングのより安定したバージョンです。
SET @rnk=0; SET @rank=0; SET @curscore=0;
SELECT score,ID,rank FROM
(
SELECT AA.*,BB.ID,
(@rnk:=@rnk+1) rnk,
(@rank:=IF(@curscore=score,@rank,@rnk)) rank,
(@curscore:=score) newscore
FROM
(
SELECT * FROM
(SELECT COUNT(1) scorecount,score
FROM scores GROUP BY score
) AAA
ORDER BY score DESC
) AA LEFT JOIN scores BB USING (score)) A;
これをサンプルデータで試してみましょう。まずここにサンプルデータがあります:
use test
DROP TABLE IF EXISTS scores;
CREATE TABLE scores
(
id int not null auto_increment,
score int not null,
primary key (id),
key score (score)
);
INSERT INTO scores (score) VALUES
(50),(40),(75),(80),(55),
(40),(30),(80),(70),(45),
(40),(30),(65),(70),(45),
(55),(45),(83),(85),(60);
サンプルデータをロードしてみましょう
mysql> DROP TABLE IF EXISTS scores;
Query OK, 0 rows affected (0.15 sec)
mysql> CREATE TABLE scores
-> (
-> id int not null auto_increment,
-> score int not null,
-> primary key (id),
-> key score (score)
-> );
Query OK, 0 rows affected (0.16 sec)
mysql> INSERT INTO scores (score) VALUES
-> (50),(40),(75),(80),(55),
-> (40),(30),(80),(70),(45),
-> (40),(30),(65),(70),(45),
-> (55),(45),(83),(85),(60);
Query OK, 20 rows affected (0.04 sec)
Records: 20 Duplicates: 0 Warnings: 0
次に、ユーザー変数を初期化します。
mysql> SET @rnk=0; SET @rank=0; SET @curscore=0;
Query OK, 0 rows affected (0.01 sec)
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
さて、これがクエリの出力です:
mysql> SELECT score,ID,rank FROM
-> (
-> SELECT AA.*,BB.ID,
-> (@rnk:=@rnk+1) rnk,
-> (@rank:=IF(@curscore=score,@rank,@rnk)) rank,
-> (@curscore:=score) newscore
-> FROM
-> (
-> SELECT * FROM
-> (SELECT COUNT(1) scorecount,score
-> FROM scores GROUP BY score
-> ) AAA
-> ORDER BY score DESC
-> ) AA LEFT JOIN scores BB USING (score)) A;
+-------+------+------+
| score | ID | rank |
+-------+------+------+
| 85 | 19 | 1 |
| 83 | 18 | 2 |
| 80 | 4 | 3 |
| 80 | 8 | 3 |
| 75 | 3 | 5 |
| 70 | 9 | 6 |
| 70 | 14 | 6 |
| 65 | 13 | 8 |
| 60 | 20 | 9 |
| 55 | 5 | 10 |
| 55 | 16 | 10 |
| 50 | 1 | 12 |
| 45 | 10 | 13 |
| 45 | 15 | 13 |
| 45 | 17 | 13 |
| 40 | 2 | 16 |
| 40 | 6 | 16 |
| 40 | 11 | 16 |
| 30 | 7 | 19 |
| 30 | 12 | 19 |
+-------+------+------+
20 rows in set (0.18 sec)
同じスコアを共有する複数のIDが同じランクを持っていることに注意してください。ランクは連続していないことにも注意してください。
試してみる !!!
SELECT
id,
Name,
1+(SELECT count(*) from table_name a WHERE a.Score > b.Score) as RNK,
Score
FROM table_name b;
1つのオプションは、USER変数を使用することです。
SET @i=0;
SELECT id, name, score, @i:=@i+1 AS rank
FROM ranking
ORDER BY score DESC;
受け入れられた回答 には潜在的な問題があります。同じスコアが2つ以上ある場合、ランキングにギャップが生じます。この変更例では:
id name score rank
1 Ida 100 2
2 Boo 58 5
3 Lala 99 3
4 Bash 102 1
5 Assem 99 3
58のスコアはランク5で、ランク4はありません。
ランキングにギャップがないことを確認する場合は、GROUP_CONCAT
でDISTINCT
を使用して、異なるスコアのリストを作成します。
SELECT id, name, score, FIND_IN_SET( score, (
SELECT GROUP_CONCAT( DISTINCT score
ORDER BY score DESC ) FROM scores)
) AS rank
FROM scores
結果:
id name score rank
1 Ida 100 2
2 Boo 58 4
3 Lala 99 3
4 Bash 102 1
5 Assem 99 3
これは、単一のユーザーのランクを取得する場合にも機能します。
SELECT id, name, score, FIND_IN_SET( score, (
SELECT GROUP_CONCAT(DISTINCT score
ORDER BY score DESC )
FROM scores )
) AS rank
FROM scores
WHERE name = 'Boo'
結果:
id name score rank
2 Boo 58 4
これが最良の答えです。
SELECT 1 + (SELECT count( * ) FROM highscores a WHERE a.score > b.score ) AS rank FROM
highscores b WHERE Name = 'Assem' ORDER BY rank LIMIT 1 ;
このクエリは次を返します:
3
このソリューションはDENSE_RANK
同点の場合:
SELECT *,
IF (@score=s.Score, @rank:=@rank, @rank:=@rank+1) rank,
@score:=s.Score score
FROM scores s,
(SELECT @score:=0, @rank:=0) r
ORDER BY points DESC
次の作業(テーブルがスコアと呼ばれていると仮定)しませんか?
SELECT COUNT(id) AS rank FROM Scores
WHERE score <= (SELECT score FROM Scores WHERE Name = "Assem")