Obtener el rango de un usuario en una tabla de puntaje

31

Tengo una tabla MySQL muy simple donde guardo las mejores puntuaciones. Se ve así:

Id     Name     Score

Hasta aquí todo bien. La pregunta es: ¿cómo obtengo el rango de usuarios? Por ejemplo, tengo usuarios Nameo Idy quiero obtener su rango, donde todas las filas están ordenadas ordinales descendiendo para Score.

Un ejemplo

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

En este mismo caso, Assemel rango sería 3, porque obtuvo el 3er puntaje más alto.

La consulta debe devolver una fila, que contiene (solo) el Rango requerido.

Miguel
fuente

Respuestas:

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

da esta lista:

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

Obtener un puntaje de persona soltera:

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'

Da este resultado:

id name score rank
5 Assem 99 3

Tendrá un escaneo para obtener la lista de puntajes y otro escaneo o buscará hacer algo útil con él. Un índice en la scorecolumna ayudaría al rendimiento en tablas grandes.

Cairnz
fuente
3
Lo correlacionado (SELECT GROUP_CONCAT(score) FROM TheWholeTable)no es la mejor manera. Y puede tener un problema con el tamaño de la fila creada.
ypercubeᵀᴹ
2
Esto fallará en caso de empate.
Arvind07
La consulta sola persona puntuación es extremadamente lento para tablas más grandes .. Una mucho mejor consulta para determinar el rango (con huecos para los lazos) para la puntuación de una sola persona es: SELECT 1 + COUNT(*) AS rank FROM scores WHERE score > (SELECT score FROM scores WHERE name='Assem'). Que 'solo' cuenta el número de entradas con una puntuación más alta que la entrada actual. (Si agrega DISTINCT, obtendrá el rango sin huecos ...)
Paul
IMPORTANTE: GROUP_CONTAT tiene un límite predeterminado de 1024 caracteres, en grandes conjuntos de datos dará como resultado rangos incorrectos, por ejemplo, podría detenerse en el rango 100 y luego reportar 0 como rango
0plus1
30

Cuando varias entradas tienen el mismo puntaje, el siguiente rango no debe ser consecutivo. El siguiente rango debe incrementarse por el número de puntajes que comparten el mismo rango.

Para mostrar puntajes así se requieren dos variables de rango

  • variable de rango para mostrar
  • variable de rango para calcular

Aquí hay una versión más estable de ranking con lazos:

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;

Probemos esto con datos de muestra. Primero, aquí están los datos de muestra:

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

Carguemos los datos de muestra

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

A continuación, vamos a inicializar las variables de usuario:

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)

Ahora, aquí está el resultado de la consulta:

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)

Tenga en cuenta que las ID múltiples que comparten el mismo puntaje tienen el mismo rango. También tenga en cuenta que el rango no es consecutivo.

Darle una oportunidad !!!

RolandoMySQLDBA
fuente
Dado que esto está utilizando variables de ámbito de sesión, ¿es seguro si, por ejemplo, varios usuarios finales solicitan el marcador al mismo tiempo? ¿Es posible que el conjunto de resultados tenga resultados diferentes porque otro usuario también está ejecutando esta consulta? Imagine una API frente a esta consulta con muchos clientes golpeándola a la vez.
Xaero Degreaz
@XaeroDegreaz Tienes razón, es posible. Imagina calcular rangos para un juego. Un usuario consulta por rango y otro consulta 5 segundos después de que una persona supera el puntaje más alto o ingresa los puntajes X más altos. No obstante, lo mismo puede suceder si la clasificación se realizó a nivel de aplicación en lugar de a nivel de servidor.
RolandoMySQLDBA
Gracias por la respuesta. Mi preocupación no es realmente si los datos cambian orgánicamente con el tiempo, me preocupa que varios usuarios que realizan la consulta estarían modificando / sobrescribiendo los datos almacenados en las variables de ámbito de sesión, mientras que otros usuarios también realizan la consulta. ¿Tiene sentido?
Xaero Degreaz
@XaeroDegreaz esa es la belleza de las variables de alcance de la sesión. Solo están en su sesión, y nadie más. No verá las variables de sesión de otros usuarios y nadie verá sus variables de sesión.
RolandoMySQLDBA
De acuerdo, eso es lo que me estaba inclinando hacia creer: que las variables de sesión están limitadas a la conexión, y una sola conexión no puede ser ocupada por más de una persona a la vez. Una vez que la conexión es libre, o arrojada nuevamente al grupo, otro usuario puede acceder a la conexión y las variables de sesión se reinicializan (al realizar esta consulta). Gracias nuevamente por la informacion.
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
fuente
9

Una opción sería usar variables de USUARIO:

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

La respuesta aceptada tiene un problema potencial. Si hay dos o más puntajes idénticos, habrá lagunas en la clasificación. En este ejemplo modificado:

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

El puntaje de 58 tiene rango 5, y no hay rango 4.

Si desea asegurarse de que no haya espacios en el ranking, el uso DISTINCTen el GROUP_CONCATde construir una lista de las puntuaciones distintas:

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

Resultado:

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

Esto también funciona para obtener el rango de un solo usuario:

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'

Resultado:

id name score rank
 2  Boo   58    4
Mahesh Lakher
fuente
La consulta de rango de un solo usuario se puede optimizar enormemente mediante el uso de COUNTuna subconsulta. Vea mi comentario en la respuesta aceptada
Paul
Buena nota y mejora. funciona muy bien
SMMousavi
3

Aquí está la mejor respuesta:

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 ;

Esta consulta devolverá:

3

FamerJoe
fuente
Estoy teniendo un pequeño problema con eso, pensé. Por ejemplo: si los dos primeros usuarios tienen puntajes diferentes y el resto tiene 0, la clasificación para las personas con puntaje cero es # 4 en lugar de # 3. Pero el primero está obteniendo correctamente el # 1 y el segundo # 2. ¿Algunas ideas?
fersarr
3

Esta solución da DENSE_RANKen caso de empate:

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
fuente
0

¿No funcionaría lo siguiente (suponiendo que su tabla se llame Puntajes)?

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

Tengo esto, que da los mismos resultados que el que tiene variables. Funciona con lazos y puede ser más rápido:

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

No lo probé, pero estoy usando uno que funciona perfectamente, que adapté a esto con las variables que estaba usando aquí.

Juan
fuente