Lấy thứ hạng của người dùng trong bảng điểm


31

Tôi có một bảng MySQL rất đơn giản, nơi tôi lưu các điểm cao. Có vẻ như thế:

Id     Name     Score

Càng xa càng tốt. Câu hỏi là: Làm thế nào để tôi có được thứ hạng người dùng? Ví dụ: tôi có một người dùng Namehoặc Idmuốn có thứ hạng của anh ta, trong đó tất cả các hàng được sắp xếp theo thứ tự giảm dần cho Score.

Một ví dụ

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

Trong trường hợp này, Assemthứ hạng của bạn sẽ là 3, vì anh ta có điểm cao thứ 3.

Truy vấn sẽ trả về một hàng, chứa (chỉ) Xếp hạng được yêu cầu.

Câu trả lời:


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

đưa ra danh sách này:

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

Lấy điểm một người:

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'

Cho kết quả này:

id name score rank
5 Assem 99 3

Bạn sẽ có một lần quét để lấy danh sách điểm và một lần quét khác hoặc tìm cách làm điều gì đó hữu ích với nó. Một chỉ mục trên scorecột sẽ giúp hiệu suất trên các bảng lớn.


3
Tương quan (SELECT GROUP_CONCAT(score) FROM TheWholeTable)không phải là cách tốt nhất. Và nó có thể có một vấn đề với kích thước của hàng được tạo.
ypercubeᵀᴹ

2
Điều này sẽ thất bại trong trường hợp quan hệ.
Arvind07

Truy vấn điểm một người cực kỳ chậm đối với các bảng lớn hơn .. Một truy vấn tốt hơn nhiều để xác định thứ hạng (có khoảng trống cho các mối quan hệ) cho điểm số của một người là : SELECT 1 + COUNT(*) AS rank FROM scores WHERE score > (SELECT score FROM scores WHERE name='Assem'). Mà 'chỉ' đếm số lượng mục có điểm cao hơn mục hiện tại. (Nếu bạn thêm DISTINCTbạn sẽ có được thứ hạng mà không có khoảng trống ..)
Paul

QUAN TRỌNG: GROUP_CONTAT có giới hạn mặc định là 1024 ký tự, trên các tập hợp dữ liệu lớn, nó sẽ dẫn đến xếp hạng sai, ví dụ: nó có thể dừng ở thứ hạng 100 và sau đó báo cáo 0 là thứ hạng
0plus1

30

Khi nhiều mục có cùng số điểm, thứ hạng tiếp theo không được liên tiếp. Thứ hạng tiếp theo nên được tăng theo số điểm chia sẻ cùng thứ hạng.

Để hiển thị điểm số như thế cần có hai biến thứ hạng

  • biến thứ hạng để hiển thị
  • biến thứ hạng để tính toán

Đây là phiên bản xếp hạng ổn định hơn với các mối quan hệ:

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;

Hãy thử điều này với dữ liệu mẫu. Đầu tiên Dưới đây là dữ liệu mẫu:

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

Hãy tải dữ liệu mẫu

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

Tiếp theo, hãy khởi tạo các biến người dùng:

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)

Bây giờ, đây là đầu ra của truy vấn:

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)

Vui lòng lưu ý cách nhiều ID chia sẻ cùng một điểm có cùng thứ hạng. Cũng lưu ý rằng thứ hạng không liên tiếp.

Hãy thử một lần !!!


Vì việc này đang sử dụng các biến trong phạm vi phiên, điều này có an toàn không nếu, giả sử, nhiều người dùng cuối đang yêu cầu bảng điểm cùng một lúc? Có thể cho tập kết quả có kết quả khác nhau vì một người dùng khác cũng đang thực hiện truy vấn này? Hãy tưởng tượng một API trước truy vấn này với nhiều khách hàng truy cập cùng một lúc.
Xaero Degreaz

@XaeroDegreaz Bạn nói đúng, Điều đó là có thể. Hãy tưởng tượng thứ hạng tính toán cho một trò chơi. Một người dùng truy vấn thứ hạng và một người dùng khác truy vấn 5 giây sau khi một người đánh bại số điểm cao hoặc vào điểm X cao nhất. Mặc dù vậy, điều tương tự có thể xảy ra nếu xếp hạng được thực hiện ở cấp ứng dụng thay vì cấp máy chủ.
RolandoMySQLDBA

Cảm ơn vi đa trả lơi. Mối quan tâm của tôi không thực sự là liệu dữ liệu có thay đổi hữu cơ theo thời gian hay không, mối quan tâm của tôi là nhiều người dùng thực hiện truy vấn sẽ sửa đổi / ghi đè dữ liệu được lưu trữ trong các biến trong phạm vi phiên trong khi những người dùng khác cũng đang thực hiện truy vấn. Điều đó có ý nghĩa?
Xaero Degreaz

@XaeroDegreaz đó là vẻ đẹp của các biến phạm vi phiên. Họ chỉ ở trong phiên của bạn, và không ai khác. Bạn sẽ không thấy các biến phiên từ những người dùng khác và không ai sẽ thấy các biến phiên của bạn.
RolandoMySQLDBA

Được rồi, đó là điều mà tôi đã nghiêng về niềm tin - các biến phiên đó nằm trong phạm vi kết nối và một kết nối duy nhất có thể bị chiếm bởi nhiều người cùng một lúc. Khi kết nối miễn phí hoặc được đưa trở lại nhóm, người dùng khác có thể nhảy vào kết nối và các biến phiên được khởi tạo lại (khi thực hiện truy vấn này). Cảm ơn vì đã cho biết thông tin.
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

Một tùy chọn sẽ là sử dụng các biến USER:

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

4

Các câu trả lời được chấp nhận có một vấn đề tiềm ẩn. Nếu có hai hoặc nhiều điểm giống nhau, sẽ có những khoảng trống trong bảng xếp hạng. Trong ví dụ sửa đổi này:

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

Điểm 58 có thứ hạng 5, và không có thứ hạng 4.

Nếu bạn muốn chắc chắn không có khoảng trống trong bảng xếp hạng, sử dụng DISTINCTtrong GROUP_CONCATxây dựng một danh sách các điểm khác biệt:

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

Kết quả:

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

Điều này cũng hoạt động để có được thứ hạng của người dùng:

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'

Kết quả:

id name score rank
 2  Boo   58    4

Truy vấn xếp hạng của một người dùng có thể được tối ưu hóa rất nhiều bằng cách sử dụng COUNTvà truy vấn con thay thế. Xem nhận xét của tôi tại Câu trả lời được chấp nhận
Paul

Lưu ý tốt và tăng cường. hoạt động rất tốt
SMMousavi

3

Đây là câu trả lời tốt nhất:

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 ;

Truy vấn này sẽ trả về:

3


Tôi đang có một vấn đề nhỏ với nó nghĩ. Ví dụ: nếu hai người dùng đầu tiên có điểm số khác nhau và tất cả những người còn lại có 0, thì thứ hạng cho những người có điểm 0 là # 4 thay vì # 3. Nhưng thứ nhất là chính xác nhận được # 1 và thứ hai # 2. Có ý kiến ​​gì không?
fersarr

3

Giải pháp này đưa ra DENSE_RANKtrong trường hợp quan hệ:

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

Sẽ không có công việc sau đây (giả sử bảng của bạn được gọi là Điểm)?

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

-4

Tôi có cái này, cho kết quả giống như cái có biến. Nó hoạt động với các mối quan hệ và nó có thể nhanh hơn:

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

Tôi đã không kiểm tra nó, nhưng tôi đang sử dụng một cái hoạt động hoàn hảo, mà tôi thích nghi với điều này với các biến bạn đang sử dụng ở đây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.