Uzyskaj rangę użytkownika w tabeli wyników


31

Mam bardzo prostą tabelę MySQL, w której zapisuję najlepsze wyniki. Wygląda to tak:

Id     Name     Score

Jak na razie dobrze. Pytanie brzmi: jak uzyskać pozycję w rankingu użytkowników? Na przykład mam użytkowników Namelub Idchcę uzyskać jego rangę, gdzie wszystkie wiersze są uporządkowane porządkowo malejąco Score.

Przykład

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

W tym przypadku Assemjego pozycja wynosiłaby 3, ponieważ otrzymał 3. najwyższy wynik.

Zapytanie powinno zwrócić jeden wiersz, który zawiera (tylko) wymaganą rangę.

Odpowiedzi:


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

daje tę listę:

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

Uzyskanie wyniku dla jednej osoby:

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'

Daje ten wynik:

id name score rank
5 Assem 99 3

Będziesz miał jeden skan, aby uzyskać listę wyników, a drugi skan lub spróbuj zrobić z nim coś pożytecznego. Indeks w scorekolumnie poprawiłby wydajność dużych tabel.


3
Skorelowany (SELECT GROUP_CONCAT(score) FROM TheWholeTable)nie jest najlepszym sposobem. Może to mieć problem z rozmiarem utworzonego wiersza.
ypercubeᵀᴹ

2
Nie powiedzie się to w przypadku powiązań.
Arvind07

Pojedynczy wynik kwerendy osoba jest bardzo powolny dla większych tabel .. dużo lepszym zapytania do określenia rangi (z przerw na krawaty) na wynik jednej osoby wynosi: SELECT 1 + COUNT(*) AS rank FROM scores WHERE score > (SELECT score FROM scores WHERE name='Assem'). Który „tylko” liczy liczbę wpisów z wyższym wynikiem niż bieżący. (Jeśli dodasz DISTINCT, dostaniesz rangę bez luk ..)
Paul

WAŻNE: GROUP_CONTAT ma domyślny limit 1024 znaków, w przypadku dużych zestawów danych spowoduje to błędne rangi, na przykład może zatrzymać się na poziomie 100, a następnie zgłosić 0 jako stopień
0plus1

30

Gdy wiele zgłoszeń ma ten sam wynik, następna pozycja nie powinna być kolejna. Następna ranga powinna być zwiększana o liczbę wyników o tej samej randze.

Aby wyświetlić takie wyniki, wymagane są dwie zmienne rang

  • zmienna rangi do wyświetlenia
  • zmienna rangi do obliczenia

Oto bardziej stabilna wersja rankingu z powiązaniami:

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;

Wypróbujmy to z przykładowymi danymi. Najpierw Oto przykładowe dane:

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

Załadujmy przykładowe dane

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

Następnie zainicjuj zmienne użytkownika:

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)

Oto wynik zapytania:

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)

Zwróć uwagę, jak wiele identyfikatorów o tym samym wyniku ma tę samą pozycję. Zauważ też, że ranga nie jest kolejna.

Spróbuj !!!


Ponieważ wykorzystuje to zmienne o zasięgu sesji, czy jest to bezpieczne, jeśli powiedzmy, że wielu użytkowników końcowych prosi o tablicę wyników w tym samym czasie? Czy jest możliwe, aby zestaw wyników miał różne wyniki, ponieważ inny użytkownik wykonuje również to zapytanie? Wyobraź sobie interfejs API przed tym zapytaniem, w który trafia wielu klientów jednocześnie.
Xaero Degreaz

@XaeroDegreaz Masz rację, jest to możliwe. Wyobraź sobie obliczanie rang dla gry. Jeden użytkownik pyta o rangę, a inny użytkownik pyta 5 sekund po tym, jak dana osoba osiągnie najwyższy wynik lub wejdzie w najwyższe wyniki X. Niezależnie od tego, to samo może się zdarzyć, jeśli ranking został przeprowadzony na poziomie aplikacji, a nie na poziomie serwera.
RolandoMySQLDBA

Dziękuję za odpowiedź. Nie martwię się tak naprawdę, czy dane zmieniają się organicznie w czasie, ale obawiam się, że wielu użytkowników wykonujących zapytanie zmodyfikuje / nadpisze dane przechowywane w zmiennych o zasięgu sesji, podczas gdy inni użytkownicy również będą wykonywać zapytanie. Czy to ma sens?
Xaero Degreaz

@XaeroDegreaz to piękno zmiennych zakresu sesji. Są tylko w twojej sesji i nikogo innego. Nie zobaczysz zmiennych sesji od innych użytkowników i nikt nie zobaczy twoich zmiennych sesji.
RolandoMySQLDBA

Okej, to było coś, co skłaniałem się ku wierzeniu - że zmienne sesji mają zasięg do połączenia, a pojedyncze połączenie nie może być zajęte przez więcej niż jedną osobę na raz. Gdy połączenie jest wolne lub wrzucone z powrotem do puli, inny użytkownik może wskoczyć na połączenie, a zmienne sesji zostaną ponownie zainicjowane (podczas wykonywania tego zapytania). Ponownie dziękuję za informację.
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

Jedną z opcji byłoby użycie zmiennych USER:

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

4

Odpowiedź Zaakceptowany ma potencjalny problem. Jeśli są dwa lub więcej identycznych wyników, w rankingu pojawią się luki. W tym zmodyfikowanym przykładzie:

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

Wynik 58 ma rangę 5, a nie ma rangi 4.

Jeśli chcesz się upewnić, że nie ma luk w rankingach, użyj DISTINCTw, GROUP_CONCATaby zbudować listę odrębnych wyników:

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

Wynik:

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

Działa to również w celu uzyskania rangi pojedynczego użytkownika:

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'

Wynik:

id name score rank
 2  Boo   58    4

Zapytanie o rangę pojedynczego użytkownika można ogromnie zoptymalizować, używając COUNTzamiast niego podzapytania. Zobacz mój komentarz do zaakceptowanej odpowiedzi
Paul

Dobra notatka i ulepszenie. działa bardzo dobrze
SMMousavi

3

Oto najlepsza odpowiedź:

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 ;

To zapytanie zwróci:

3)


Mam z tym mały problem. Na przykład: jeśli dwóch pierwszych użytkowników ma różne wyniki, a wszyscy pozostali mają 0, rankingi osób z wynikiem zerowym to # 4 zamiast # 3. Ale pierwszy poprawnie otrzymuje numer 1, a drugi nr 2. Jakieś pomysły?
fersarr

3

To rozwiązanie daje DENSE_RANKw przypadku więzi:

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

Czy poniższe czynności nie zadziałałyby (zakładając, że twoja tabela nazywa się Wyniki)?

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

-4

Mam to, co daje takie same wyniki jak ten ze zmiennymi. Działa z więzami i może być szybszy:

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

Nie testowałem tego, ale używam takiego, który działa idealnie, który dostosowałem do tego ze zmiennymi, których tu używałeś.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.