Ottieni il grado di un utente in una tabella dei punteggi


31

Ho una tabella MySQL molto semplice in cui salvo i punteggi migliori. Sembra che:

Id     Name     Score

Fin qui tutto bene. La domanda è: come posso ottenere il ranking degli utenti? Ad esempio, ho un utente Nameo Ide voglio ottenere il suo grado, in cui tutte le righe sono ordinate in ordine decrescente per il Score.

Un esempio

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

In questo caso, Assemil grado sarebbe 3, perché ha ottenuto il 3 ° punteggio più alto.

La query dovrebbe restituire una riga, che contiene (solo) il rango richiesto.

Risposte:


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

dà questo elenco:

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

Ottenere un punteggio per una sola persona:

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à questo risultato:

id name score rank
5 Assem 99 3

Avrai una scansione per ottenere l'elenco dei punteggi e un'altra scansione o cercare di fare qualcosa di utile con esso. Un indice sulla scorecolonna aiuterebbe le prestazioni su tabelle di grandi dimensioni.


3
Il correlato (SELECT GROUP_CONCAT(score) FROM TheWholeTable)non è il modo migliore. E potrebbe avere un problema con la dimensione della riga creata.
ypercubeᵀᴹ

2
Questo fallirà in caso di legami.
Arvind07,

La singola query persona punteggio è estremamente lento per le tabelle più grandi .. una migliore query per determinare rango (con spazi per cravatte) per il punteggio di una singola persona è: SELECT 1 + COUNT(*) AS rank FROM scores WHERE score > (SELECT score FROM scores WHERE name='Assem'). Quale 'solo' conta il numero di voci con un punteggio più alto rispetto alla voce corrente. (Se aggiungi DISTINCTotterrai il grado senza lacune ..)
Paul

IMPORTANTE: GROUP_CONTAT ha un limite predefinito di 1024 caratteri, su grandi insiemi di dati comporterà ranghi errati, ad esempio, potrebbe fermarsi al rango 100 e quindi riportare 0 come rango
0plus1

30

Quando più voci hanno lo stesso punteggio, il rango successivo non dovrebbe essere consecutivo. Il rango successivo dovrebbe essere incrementato del numero di punteggi che condividono lo stesso rango.

Per visualizzare punteggi del genere sono necessarie due variabili di classificazione

  • rango variabile da visualizzare
  • rango variabile da calcolare

Ecco una versione più stabile della classifica con legami:

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;

Proviamo con dati di esempio. Prima Ecco i dati di esempio:

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

Carichiamo i dati di esempio

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

Quindi, inizializzare le variabili utente:

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)

Ora, ecco l'output della query:

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)

Si noti che più ID che condividono lo stesso punteggio hanno lo stesso rango. Si noti inoltre che il rango non è consecutivo.

Provaci !!!


Dato che utilizza variabili con ambito sessione, è sicuro se, per esempio, più utenti finali richiedono contemporaneamente il quadro di valutazione? È possibile che il set di risultati abbia risultati diversi perché anche un altro utente sta eseguendo questa query? Immagina un'API di fronte a questa query con molti client che la colpiscono contemporaneamente.
Xaero Degreaz,

@XaeroDegreaz Hai ragione, è possibile. Immagina di calcolare i ranghi di una partita. Un utente richiede il rango e un altro utente richiede 5 secondi dopo che una persona batte il punteggio più alto o inserisce i punteggi X più alti. Ciò nonostante, lo stesso può accadere se la classificazione fosse effettuata a livello di applicazione anziché a livello di server.
RolandoMySQLDBA

Grazie per la risposta. La mia preoccupazione non è davvero se i dati si spostino organicamente nel tempo, la mia preoccupazione è che più utenti che eseguono la query modifichino / sovrascrivendo i dati memorizzati nelle variabili con ambito sessione mentre anche altri utenti stanno eseguendo la query. Ha senso?
Xaero Degreaz,

@XaeroDegreaz questa è la bellezza delle variabili dell'ambito della sessione. Sono solo nella tua sessione e nessun altro. Non vedrai le variabili di sessione di altri utenti e nessuno vedrà le tue variabili di sessione.
RolandoMySQLDBA

Ok, questo è quello che stavo propendendo a credere: le variabili di sessione sono mirate alla connessione e una singola connessione non può essere occupata da più di una persona alla volta. Una volta che la connessione è libera o reimmessa nel pool, un altro utente può saltare sulla connessione e le variabili di sessione vengono reinizializzate (durante l'esecuzione di questa query). Grazie ancora per l'informazione.
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

Un'opzione sarebbe quella di utilizzare le variabili USER:

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

4

La risposta accettata ha un potenziale problema. Se ci sono due o più punteggi identici, ci saranno delle lacune nella classifica. In questo esempio modificato:

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

Il punteggio di 58 ha il grado 5 e non esiste il grado 4.

Se si vuole fare che non vi siano lacune nelle classifiche, l'uso DISTINCTnel GROUP_CONCATcostruire una lista dei punteggi distinti:

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

Risultato:

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

Questo funziona anche per ottenere il rango di un singolo utente:

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'

Risultato:

id name score rank
 2  Boo   58    4

La query di rango del singolo utente può essere ottimizzata enormemente usando COUNTe una subquery invece. Vedi il mio commento alla risposta accettata
Paul

Buona nota e miglioramento. funziona molto bene
SMMousavi il

3

Ecco la risposta migliore:

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 ;

Questa query restituirà:

3


Sto avendo un piccolo problema con questo pensiero. Ad esempio: se i primi due utenti hanno punteggi diversi e tutti gli altri hanno 0, la classifica per le persone con punteggio zero è # 4 anziché # 3. Ma il primo sta ottenendo correttamente il n. 1 e il secondo n. 2. Qualche idea?
Fersarr,

3

Questa soluzione fornisce DENSE_RANKin caso di legami:

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

Non funzionerebbe il seguente (supponendo che il tuo tavolo sia chiamato Punteggi)?

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

-4

Ho questo, che dà gli stessi risultati di quello con le variabili. Funziona con i legami e potrebbe essere più veloce:

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

Non l'ho provato, ma ne sto usando uno che funziona perfettamente, che mi sono adattato a questo con le variabili che stavi usando qui.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.