Отримайте звання користувача в таблиці результатів


31

У мене дуже проста таблиця MySQL, де я зберігаю рекорди. Це виглядає так:

Id     Name     Score

Все йде нормально. Питання: Як отримати рейтинг користувачів? Наприклад, у мене є користувачі Nameабо Idхочу отримати його ранг, де всі рядки впорядковані в порядку зменшення для Score.

Приклад

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

У цьому випадку, Assemрейтинг був би 3, оскільки він отримав 3-ту найвищу оцінку.

Запит повинен повернути один рядок, який містить (лише) необхідний ранг.

Відповіді:


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

надає цей список:

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

Отримання бала з однією людиною:

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'

Дає такий результат:

id name score rank
5 Assem 99 3

У вас буде одне сканування для отримання списку балів, а інше сканування або прагнення зробити щось корисне з ним. Індекс у scoreстовпці сприятиме ефективності роботи великих таблиць.


3
Корельований (SELECT GROUP_CONCAT(score) FROM TheWholeTable)- не найкращий спосіб. І може виникнути проблема з розміром створеного рядка.
ypercubeᵀᴹ

2
Це не вдасться у разі зв’язків.
Arvind07

Єдина людина , оцінка запити вкрай повільні для великих таблиць .. А набагато краще запиту , щоб визначити ранг (з зазорами для зв'язку) для окулярів одну людину становить: SELECT 1 + COUNT(*) AS rank FROM scores WHERE score > (SELECT score FROM scores WHERE name='Assem'). Котрий "просто" підраховує кількість записів з більшою оцінкою, ніж поточний запис. (Якщо додати, DISTINCTви отримаєте звання без прогалин ..)
Павло

ВАЖЛИВО: GROUP_CONTAT має ліміт за замовчуванням 1024 символи, для великих наборів даних це призведе до неправильних рангів, наприклад, він може зупинитися на ранзі 100 і потім повідомити 0 як ранг
0plus1

30

Якщо кілька записів мають однаковий бал, наступний ранг не повинен бути послідовним. Наступний ранг слід збільшувати на кількість балів, що мають однаковий ранг.

Для відображення подібних балів потрібні дві змінні рейтингу

  • змінна позиція для відображення
  • змінну ранжу для обчислення

Ось більш стабільна версія рейтингу з зв'язками:

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;

Давайте спробуємо це зі зразковими даними. По-перше Ось вибіркові дані:

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

Давайте завантажимо зразкові дані

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

Далі дозвольте ініціалізувати користувацькі змінні:

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)

Тепер ось вихід запиту:

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)

Зверніть увагу, як декілька ідентифікаторів, які мають один і той же бал, мають однаковий ранг. Також зауважте, що ранг не є послідовним.

Спробувати !!!


Оскільки це використовує змінні, пов’язані з сеансом, чи безпечно це, якщо, скажімо, кілька кінцевих користувачів одночасно запитують табло? Чи можливо, що набір результатів має різні результати, оскільки інший користувач також виконує цей запит? Уявіть API перед цим запитом, коли багато клієнтів отримують його відразу.
Xaero Degreaz

@XaeroDegreaz Ви праві, можливо. Уявіть собі обчислення рангів для гри. Один користувач запитує про ранг, а інший користувач запитує 5 секунд після того, як людина перемагає високий бал або вводить найкращі результати X. Незважаючи на те, те саме може статися, якщо ранжирування проводилося на рівні програми, а не на рівні сервера.
RolandoMySQLDBA

Дякую за відповідь. Моя стурбованість насправді не полягає в тому, чи органічно змінюються дані з часом, чи я переживаю, що кілька користувачів, які виконують запит, модифікують / перезаписують дані, що зберігаються в змінних, пов’язаних із сеансом, а інші користувачі також виконують запит. Чи має це сенс?
Xaero Degreaz

@XaeroDegreaz - це краса змінних областей сеансу. Вони лише у вашому сеансі, і ні в кого іншого. Ви не побачите змінні сеансу від інших користувачів, і ніхто не побачить ваші змінні сеансу.
RolandoMySQLDBA

Гаразд, це те, на що я схилявся до того, що я вважаю, що змінні сеансу відносяться до з'єднання, і одночасне з'єднання не може займати більше однієї людини одночасно. Після того, як з'єднання буде безкоштовним або перекинуто назад у пул, інший користувач може перейти на з'єднання і змінні сеансу повторно ініціалізуються (під час виконання цього запиту). Ще раз дякую за інформацію.
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

Одним із варіантів було б використання змінних USER:

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

4

Загальноприйнятий відповідь має потенційну проблему. Якщо є два чи більше однакових балів, у рейтингу будуть прогалини. У цьому модифікованому прикладі:

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

Оцінка 58 має ранг 5, а 4 рангу немає.

Якщо ви хочете переконатись у відсутності прогалин у рейтингу, використовуйте DISTINCTв, GROUP_CONCATщоб скласти список різних балів:

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

Результат:

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

Це також працює для отримання одного рейтингу користувача:

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'

Результат:

id name score rank
 2  Boo   58    4

Запит на рейтинг одного користувача можна надзвичайно оптимізувати, використовуючи COUNTзамість нього підзапит. Дивіться мій коментар у Прийнятому відповіді
Павло

Гарна нота та покращення. працює дуже добре
SMMousavi

3

Ось найкраща відповідь:

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 ;

Цей запит поверне:

3


У мене виникає невелика проблема з цією думкою. Наприклад: якщо перші два користувачі мають різні бали, а всі інші мають 0, рейтинг для людей з нульовим балом - №4 замість №3. Але перший правильно отримує №1, а другий - №2. Будь-які ідеї?
fersarr

3

Це рішення дає DENSE_RANKу випадку зв’язків:

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

Не вдалося б виконати наступну роботу (якщо припустити, що ваша таблиця називається «Оцінки»)?

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

-4

У мене це є, що дає ті ж результати, що і у змінних. Це працює з зв'язками, і це може бути швидше:

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

Я не перевіряв це, але я використовую той, який працює ідеально, і я адаптував це до змінних, які ви тут використовували.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.