Получить звание пользователя в таблице результатов


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

Не будет ли следующая работа (если ваша таблица называется Scores)?

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.