スコアテーブルでユーザーのランクを取得する


31

ハイスコ​​アを保存する非常にシンプルなMySQLテーブルがあります。次のようになります。

Id     Name     Score

ここまでは順調ですね。質問は次のとおりです。ユーザーランクを取得するにはどうすればよいですか。たとえば、ユーザーがいるNameId、またはランクを取得したい場合、すべての行がの降順で順序付けられScoreます。

Id  Name    Score
1   Ida     100
2   Boo     58
3   Lala    88
4   Bash    102
5   Assem   99

この場合、Assem彼は3番目に高いスコアを得たため、ランクは3になります。

クエリは、必要な(のみ)ランクを含む1つの行を返す必要があります。

回答:


31
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列のインデックスは、大きなテーブルのパフォーマンスに役立ちます。


3
相関(SELECT GROUP_CONCAT(score) FROM TheWholeTable)は最良の方法ではありません。また、作成された行のサイズに問題がある可能性があります。
ypercubeᵀᴹ

2
同点の場合、これは失敗します。
-Arvind07

一人の人のスコアクエリは、大きなテーブルでは非常に遅くなります。一人の人のスコアのランク(絆のギャップ)を決定するためのはるかに優れたクエリは次のとおりSELECT 1 + COUNT(*) AS rank FROM scores WHERE score > (SELECT score FROM scores WHERE name='Assem')です。これは、現在のエントリよりも高いスコアのエントリの数を数えるだけです。(追加DISTINCTすると、ギャップなしでランクが取得されます。)
ポール

重要:GROUP_CONTATは、1024文字のデフォルトの制限があり、それは間違ったランクになります大量のデータセットに対して、例えば、それはランク100で停止することがありますし、次にランクとして0を報告
0plus1

30

複数のエントリのスコアが同じ場合、次のランクは連続してはなりません。次のランクは、同じランクを共有するスコアの数だけ増やす必要があります。

このようなスコアを表示するには、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が同じランクを持っていることに注意してください。また、ランクは連続していないことに注意してください。

試してみる !!!


これはセッションスコープの変数を使用しているため、たとえば複数のエンドユーザーが同時にスコアボードを要求している場合、これは安全ですか?別のユーザーもこのクエリを実行しているため、結果セットに異なる結果が生じる可能性はありますか?多くのクライアントが一度にヒットするこのクエリの前にあるAPIを想像してください。
Xaero Degreaz

@XaeroDegreazそのとおりです、可能です。ゲームのランクを計算することを想像してください。あるユーザーがランクを照会し、別のユーザーがユーザーがハイスコアを破ったか上位Xスコアを入力してから5秒後に照会します。それにもかかわらず、ランキングがサーバーレベルではなくアプリケーションレベルで行われた場合にも同じことが起こります。
RolandoMySQLDBA

返信いただきありがとうございます。私の懸念は、データが経時的に有機的にシフトするかどうかではなく、クエリを実行する複数のユーザーがセッションスコープ変数に格納されているデータを変更/上書きする一方で、他のユーザーもクエリを実行することです。それは理にかなっていますか?
Xaero Degreaz

@XaeroDegreazは、セッションスコープ変数の美しさです。それらはあなたのセッションにのみあり、他の誰にもありません。他のユーザーからのセッション変数は表示されず、セッション変数は誰にも表示されません。
RolandoMySQLDBA

さて、それは私が信じることに傾いたものです-セッション変数は接続にスコープされており、単一の接続は一度に複数の人が占有することはできません。接続が解放されるか、プールに戻されると、別のユーザーが接続をホップでき、セッション変数が再初期化されます(このクエリの実行時)。情報をありがとう。
Xaero Degreaz

13
SELECT 
    id, 
    Name,
    1+(SELECT count(*) from table_name a WHERE a.Score > b.Score) as RNK,
    Score
FROM table_name b;

9

1つのオプションは、USER変数を使用することです。

SET @i=0;
SELECT id, name, score, @i:=@i+1 AS rank 
 FROM ranking 
 ORDER BY score DESC;

4

受け入れ答えは、潜在的な問題があります。同じスコアが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はありません。

ランキングにギャップがないことを確認する場合は、を使用DISTINCTしてGROUP_CONCAT個別のスコアのリストを作成します。

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

COUNT代わりにサブクエリを使用することにより、単一ユーザーのランククエリを非常に最適化できます。受け入れられた回答の私のコメントを参照してください
ポール

良い注意と強化。非常にうまく機能
SMMousavi

3

これがベストアンサーです。

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


私はそれについて小さな問題を抱えています。たとえば、最初の2人のユーザーのスコアが異なり、残りのすべてのユーザーのスコアが0の場合、ゼロスコアの人のランキングは#3ではなく#4です。しかし、最初は正しく#1を取得し、2番目は#2​​を正しく取得しています。何か案は?
ファーサー14

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

0

次の作業はうまくいかないでしょうか(テーブルがスコアと呼ばれていると仮定して)?

SELECT COUNT(id) AS rank FROM Scores 
WHERE score <= (SELECT score FROM Scores WHERE Name = "Assem")

-4

私はこれを持っています、それは変数を持つものと同じ結果を与えます。それはタイで動作し、より高速かもしれません:

SELECT COUNT(*)+1 as rank
FROM 
(SELECT score FROM scores ORDER BY score) AS sc
WHERE score <
(SELECT score FROM scores WHERE Name="Assem")

私はそれをテストしませんでしたが、完璧に機能するものを使用しています。ここで使用している変数でこれに適応しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.