Obtenez le rang d'un utilisateur dans un tableau de score

31

J'ai une table MySQL très simple où j'enregistre les meilleurs scores. Cela ressemble à ça:

Id     Name     Score

Jusqu'ici tout va bien. La question est: comment obtenir le classement d'un utilisateur? Par exemple, j'ai un utilisateur Nameou Idet je veux obtenir son rang, où toutes les lignes sont ordonnées en ordre décroissant pour le Score.

Un exemple

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

Dans ce cas précis, Assemle rang de 'serait 3, car il a obtenu le 3e score le plus élevé.

La requête doit renvoyer une ligne, qui contient (uniquement) le rang requis.

Michael
la source

Réponses:

31
SELECT id, name, score, FIND_IN_SET( score, (
SELECT GROUP_CONCAT( score
ORDER BY score DESC ) 
FROM scores )
) AS rank
FROM scores

donne cette liste:

id name  score rank
1  Ida   100   2
2  Boo    58   5
3  Lala   88   4
4  Bash  102   1
5  Assem  99   3

Obtenir un score pour une personne seule:

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'

Donne ce résultat:

id name score rank
5 Assem 99 3

Vous aurez un scan pour obtenir la liste des scores, et un autre scan ou chercher à faire quelque chose d'utile avec. Un index sur la scorecolonne améliorerait les performances sur les grandes tables.

cairnz
la source
3
La corrélation (SELECT GROUP_CONCAT(score) FROM TheWholeTable)n'est pas la meilleure façon. Et cela peut avoir un problème avec la taille de la ligne créée.
ypercubeᵀᴹ
2
Cela échouera en cas d'égalité.
Arvind07
La requête de score d'une seule personne est extrêmement lente pour les tables plus grandes SELECT 1 + COUNT(*) AS rank FROM scores WHERE score > (SELECT score FROM scores WHERE name='Assem'). Lequel "juste" compte le nombre d'entrées avec un score plus élevé que l'entrée actuelle. (Si vous ajoutez, DISTINCTvous obtiendrez le classement sans lacunes ..)
Paul
IMPORTANT: GROUP_CONTAT a une limite par défaut de 1024 caractères, sur de grands ensembles de données, il en résultera des mauvais classements, par exemple, il pourrait s'arrêter au rang 100 et signaler 0 comme rang
0plus1
30

Lorsque plusieurs entrées ont le même score, le rang suivant ne doit pas être consécutif. Le rang suivant doit être incrémenté du nombre de scores partageant le même rang.

Pour afficher des scores comme celui-ci, il faut deux variables de classement

  • variable de rang à afficher
  • variable de rang à calculer

Voici une version plus stable du classement avec égalité:

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;

Essayons cela avec des exemples de données. Voici d'abord les exemples de données:

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);

Chargeons les exemples de données

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

Ensuite, laissez initialiser les variables utilisateur:

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)

Maintenant, voici la sortie de la requête:

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)

Veuillez noter que plusieurs identifiants qui partagent le même score ont le même rang. Notez également que le classement n'est pas consécutif.

Essaie !!!

RolandoMySQLDBA
la source
Comme cela utilise des variables de portée de session, est-ce sûr si, par exemple, plusieurs utilisateurs finaux demandent le tableau de bord en même temps? Est-il possible que le jeu de résultats ait des résultats différents car un autre utilisateur exécute également cette requête? Imaginez une API devant cette requête avec de nombreux clients la frappant à la fois.
Xaero Degreaz
@XaeroDegreaz Vous avez raison, c'est possible. Imaginez le calcul des rangs pour un match. Un utilisateur demande le classement et un autre utilisateur l'interroge 5 secondes après qu'une personne ait battu le meilleur score ou saisi les X meilleurs scores. Néanmoins, la même chose peut se produire si le classement a été effectué au niveau de l'application plutôt qu'au niveau du serveur.
RolandoMySQLDBA
Merci pour la réponse. Ma préoccupation n'est pas vraiment de savoir si les données changent de manière organique au fil du temps, je crains que plusieurs utilisateurs exécutant la requête modifient / écrasent les données stockées dans les variables de portée de session tandis que d'autres utilisateurs exécutent également la requête. Cela a-t-il du sens?
Xaero Degreaz
@XaeroDegreaz, c'est la beauté des variables de portée de session. Ils ne sont que dans votre session et personne d'autre. Vous ne verrez pas les variables de session des autres utilisateurs et personne ne verra vos variables de session.
RolandoMySQLDBA
D'accord, c'est ce que je voulais en quelque sorte croire - que les variables de session sont limitées à la connexion, et qu'une seule connexion ne peut pas être occupée par plus d'une personne à la fois. Une fois la connexion libre ou renvoyée dans le pool, un autre utilisateur peut sauter sur la connexion et les variables de session sont réinitialisées (lors de l'exécution de cette requête). Merci encore pour l'information.
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;
a1ex07
la source
9

Une option serait d'utiliser des variables USER:

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

La réponse acceptée a un problème potentiel. S'il y a deux scores identiques ou plus, il y aura des lacunes dans le classement. Dans cet exemple modifié:

 id name  score rank
 1  Ida   100   2
 2  Boo    58   5
 3  Lala   99   3
 4  Bash  102   1
 5  Assem  99   3

Le score de 58 a le rang 5, et il n'y a pas de rang 4.

Si vous voulez vous assurer qu'il n'y a pas de lacunes dans les classements, utilisez- DISTINCTle GROUP_CONCATpour construire une liste de scores distincts:

SELECT id, name, score, FIND_IN_SET( score, (
SELECT GROUP_CONCAT( DISTINCT score
ORDER BY score DESC ) FROM scores)
) AS rank
FROM scores

Résultat:

id name  score rank
1  Ida   100   2
2  Boo    58   4
3  Lala   99   3   
4  Bash  102   1
5  Assem  99   3

Cela fonctionne également pour obtenir le rang d'un seul utilisateur:

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'

Résultat:

id name score rank
 2  Boo   58    4
Mahesh Lakher
la source
La requête de classement de l'utilisateur unique peut être considérablement optimisée à l'aide de COUNTet d'une sous-requête à la place. Voir mon commentaire à la réponse acceptée
Paul
Bonne note et amélioration. fonctionne très bien
SMMousavi
3

Voici la meilleure réponse:

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 ;

Cette requête renverra:

3

FamerJoe
la source
J'ai un petit problème avec cette pensée. Par exemple: si les deux premiers utilisateurs ont des scores différents et que tous les autres ont 0, le classement des personnes à score nul est # 4 au lieu de # 3. Mais le premier obtient correctement # 1 et le second # 2. Des idées?
fersarr
3

Cette solution donne DENSE_RANKen cas d'égalité:

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
Arvind07
la source
0

Le travail suivant ne fonctionnerait-il pas (en supposant que votre table s'appelle Scores)?

SELECT COUNT(id) AS rank FROM Scores 
WHERE score <= (SELECT score FROM Scores WHERE Name = "Assem")
bfredo123
la source
-4

J'ai ceci, qui donne les mêmes résultats que celui avec des variables. Cela fonctionne avec des liens et cela peut être plus rapide:

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

Je ne l'ai pas testé, mais j'en utilise un qui fonctionne parfaitement, que j'ai adapté à cela avec les variables que vous utilisiez ici.

Juan
la source