Obter a classificação de um usuário em uma tabela de pontuação

31

Eu tenho uma tabela MySQL muito simples, onde eu salvo recordes. Parece que:

Id     Name     Score

Por enquanto, tudo bem. A pergunta é: como obtenho a classificação dos usuários? Por exemplo, eu tenho um usuário Nameoue Iddeseja obter sua classificação, onde todas as linhas são ordinais ordenadas decrescentes para o Score.

Um exemplo

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

Nesse mesmo caso, Assema classificação seria 3, porque ele obteve a 3ª maior pontuação.

A consulta deve retornar uma linha, que contém (apenas) a classificação necessária.

Michael
fonte

Respostas:

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

fornece 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

Obtendo uma pontuação de uma pessoa:

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'

Dá este resultado:

id name score rank
5 Assem 99 3

Você terá uma verificação para obter a lista de pontuações e outra verificação ou buscará fazer algo útil com ela. Um índice na scorecoluna ajudaria o desempenho em tabelas grandes.

cairnz
fonte
3
O correlato (SELECT GROUP_CONCAT(score) FROM TheWholeTable)não é o melhor caminho. E isso pode ter um problema com o tamanho da linha criada.
ypercubeᵀᴹ
2
Isso falhará em caso de empate.
Arvind07
A consulta pontuação única pessoa é extremamente lento para tabelas maiores .. Muito melhor consulta para determinar a classificação (com lacunas para laços) para a pontuação de uma única pessoa é: SELECT 1 + COUNT(*) AS rank FROM scores WHERE score > (SELECT score FROM scores WHERE name='Assem'). Qual 'apenas' conta o número de entradas com uma pontuação mais alta que a entrada atual. (Se você adicionar DISTINCT, obterá a classificação sem lacunas ..)
Paul
IMPORTANTE: GROUP_CONTAT tem um limite padrão de 1024 caracteres; em grandes conjuntos de dados, resultará em classificações incorretas; por exemplo, pode parar na classificação 100 e, em seguida, reportar 0 como a classificação
0plus1
30

Quando várias entradas têm a mesma pontuação, a próxima classificação não deve ser consecutiva. A próxima classificação deve ser incrementada pelo número de pontuações que compartilham a mesma classificação.

Para exibir pontuações como essa, são necessárias duas variáveis ​​de classificação

  • variável de classificação a ser exibida
  • variável de classificação a ser calculada

Aqui está uma versão mais estável do ranking com empates:

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;

Vamos tentar isso com dados de amostra. Primeiro, aqui estão os dados de amostra:

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

Vamos carregar os dados da amostra

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

Em seguida, vamos inicializar as variáveis ​​do usuário:

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)

Agora, aqui está a saída da 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)

Observe como vários IDs que compartilham a mesma pontuação têm a mesma classificação. Observe também que a classificação não é consecutiva.

De uma chance !!!

RolandoMySQLDBA
fonte
Como isso usa variáveis ​​com escopo de sessão, isso é seguro se, por exemplo, vários usuários finais estiverem solicitando o placar ao mesmo tempo? É possível que o conjunto de resultados tenha resultados diferentes porque outro usuário também está executando esta consulta? Imagine uma API na frente desta consulta, com muitos clientes acessando-a de uma só vez.
Xaero Degreaz
@XaeroDegreaz Você está certo, é possível. Imagine calcular as fileiras para um jogo. Um usuário consulta a classificação e outro consulta 5 segundos depois que uma pessoa supera a pontuação mais alta ou insere a pontuação mais alta do X. Não obstante, o mesmo pode acontecer se a classificação for feita no nível do aplicativo e não no nível do servidor.
RolandoMySQLDBA
Obrigado pela resposta. Minha preocupação não é realmente se os dados mudam organicamente ou não ao longo do tempo, minha preocupação é que vários usuários que executam a consulta modifiquem / substituam os dados armazenados nas variáveis ​​do escopo da sessão, enquanto outros usuários também estão realizando a consulta. Isso faz sentido?
Xaero Degreaz
@XaeroDegreaz é a beleza das variáveis ​​de escopo da sessão. Eles estão apenas na sua sessão e na de mais ninguém. Você não verá variáveis ​​de sessão de outros usuários e ninguém verá suas variáveis ​​de sessão.
RolandoMySQLDBA
Ok, era nisso que eu estava mais inclinado a acreditar - que as variáveis ​​da sessão têm escopo definido para a conexão e uma conexão única não pode ser ocupada por mais de uma pessoa por vez. Quando a conexão estiver livre ou lançada de volta ao pool, outro usuário poderá entrar na conexão e as variáveis ​​da sessão serão reinicializadas (ao executar esta consulta). Mais uma vez obrigado pela informação.
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
fonte
9

Uma opção seria usar variáveis ​​USER:

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

A resposta aceita tem um problema em potencial. Se houver duas ou mais pontuações idênticas, haverá lacunas na classificação. Neste exemplo 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

A pontuação de 58 tem classificação 5, e não há classificação 4.

Se você deseja garantir que não haja lacunas nas classificações, use DISTINCTna GROUP_CONCATpara criar uma lista de pontuações 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

Isso também funciona para obter a classificação de um único usuário:

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
fonte
A consulta de classificação do usuário único pode ser enormemente otimizada usando COUNTuma subconsulta. Veja meu comentário na Resposta Aceita
Paul
Boa nota e aprimoramento. funciona muito bem
SMMousavi 15/07
3

Aqui está a melhor resposta:

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 retornará:

3

FamerJoe
fonte
Estou tendo um pequeno problema com o pensamento. Por exemplo: se os dois primeiros usuários tiverem pontuações diferentes e todos os demais tiverem 0, a classificação das pessoas com pontuação zero será a nº 4 em vez da nº 3. Mas o primeiro está recebendo corretamente o número 1 e o segundo número 2. Alguma ideia?
precisa saber é
3

Esta solução fornece DENSE_RANKno caso de empates:

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

O trabalho a seguir não (assumindo que sua tabela se chama Pontuações)?

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

Eu tenho isso, que dá os mesmos resultados que aquele com variáveis. Funciona com laços e pode ser mais 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")

Não testei, mas estou usando um que funciona perfeitamente, que me adaptei a isso com as variáveis ​​que você estava usando aqui.

Juan
fonte