점수 표에서 사용자의 순위를 가져옵니다.


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하면 격차없이 순위를 얻을 수 있습니다 ..)
Paul

중요 : 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)

동일한 점수를 공유하는 여러 ID가 동일한 순위를 갖는 방법에 유의하십시오. 또한 순위는 연속적이지 않습니다.

시도 해봐 !!!


세션 범위 변수를 사용하고 있기 때문에 여러 최종 사용자가 동시에 스코어 보드를 요청하는 경우 안전합니까? 다른 사용자도이 쿼리를 실행하기 때문에 결과 집합에 다른 결과가있을 수 있습니까? 많은 고객이 한 번에 API를 사용하여이 쿼리 앞에 API를 상상해보십시오.
Xaero Degreaz

@XaeroDegreaz 당신이 옳습니다, 가능합니다. 게임의 순위를 계산한다고 상상해보십시오. 한 사용자가 순위를 쿼리하고 다른 사용자가 사람이 최고 점수를 초과하거나 상위 X 점수를 입력 한 후 5 초 후에 쿼리합니다. 그럼에도 불구하고 순위가 서버 수준이 아닌 응용 프로그램 수준에서 수행 된 경우에도 마찬가지입니다.
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대신 하위 쿼리를 사용하여 엄청나게 최적화 할 수 있습니다 . 수락 된 답변에 대한 내 의견보기
Paul

좋은 메모 및 향상. 잘 작동
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 ;

이 쿼리는 다음을 반환합니다.


나는 생각에 작은 문제가 있습니다. 예를 들어, 처음 두 사용자의 점수가 다르고 나머지 사용자의 점수가 모두 0이면 점수가 0 인 사람의 순위는 # 3 대신 # 4입니다. 그러나 첫 번째는 # 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")

나는 그것을 테스트하지 않았지만 완벽하게 작동하는 것을 사용하고 있는데, 여기에 사용중인 변수로 이것에 적응했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.