I have a table which has float score, and I want to rank them from largest to smallest, if the same score, same ranking. I am using MySQL/MySQL Workbench, and any good ideas are appreciated.
Here is a sample input and output,
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
Tried the following query, but not working since it does not handle duplicate,
SELECT id, score,
#curRank := #curRank + 1 AS rank
FROM TestRank tr, (SELECT #curRank := 0) r
ORDER BY score desc;
In this above query, user 3 and user 5 have the same score value 4, but ranked differently.
I also tried the following query to just rank score itself, and it returns very weird results,
set #curRank := 0;
SELECT distinct score, #curRank := #curRank+1 as rank
FROM TestRank tr
ORDER BY score desc;
thanks in advance,
Lin
Check out this fiddle : http://sqlfiddle.com/#!9/17a49/3
Here's the query that will work for you:
SELECT
s.score, scores_and_ranks.rank
FROM
scores s
JOIN
(
SELECT
score_primary.score, COUNT(DISTINCT score_higher.score) + 1 AS rank
FROM
scores score_primary
LEFT JOIN scores score_higher ON score_higher.score > score_primary.score
GROUP BY score_primary.score
) scores_and_ranks
ON s.score = scores_and_ranks.score
ORDER BY rank ASC
In the "scores_and_ranks" inner query, we total up the number of distinct scores that are better than the current score. The top score will have zero, so we add 1 to get the rank value you want.
The reason we have to join to that table (using table "s") is to make sure the duplicate score values (two rows with score=4, for example) are shown in distinct rows.
You can do this by "remembering" the previous score:
SELECT id, score,
(#curRank := if(#s = score, #curRank + 1,
if(#s := score, 1, 1)
)
) as rank
FROM TestRank tr CROSS JOIN
(SELECT #curRank := 0, #s := -1) r
ORDER BY score desc;
Related
Lets say we have a score table from a sport competition:
-----------------------------------------
nickname | challenge | score | rank
-----------------------------------------
Sporty | 3 | 37283 | 1
Performer | 2 | 32319 | 2
John | 5 | 21021 | 3
Sandra | 3 | 12320 | 4
The query I use:
SELECT nickname,
challenge,
score,
#rank := #rank + 1 AS rank FROM rankings,
(SELECT #rank := 0) r
ORDER BY rank desc
I want to reorder all columns but keep the rankinks by score. For example
the table should be ordered by nickname like this:
-----------------------------------------
nickname | challenge | score | rank
-----------------------------------------
John | 5 | 21021 | 3
Performer | 2 | 32319 | 2
Sandra | 3 | 12320 | 4
sporty | 3 | 37283 | 1
I'm using MySQL 5.7, so I cannot use the rankings-functionality in MySQL 8.
How can I achive this?
Use a subquery:
SELECT nickname, challenge, score, rnk
FROM
(
SELECT nickname, challenge, score,
#rank := #rank + 1 AS rnk
FROM rankings, (SELECT #rank := 0) r
ORDER BY rnk DESC
) t
ORDER BY nickname;
Demo
The idea here is to first materialize the ranking column inside the subquery. Then, we can order that by some other column on the outside. Note that I avoid using the alias rank, because starting in MySQL, RANK is the name of an analytic function.
I've a table items listing different elements with id, popularity and rank columns.
popularity column contains an int allowing to sort elements by popularity.
I've made a query to sort by popularity and set a rank for each entry:
SELECT id,
#curRank := #curRank + 1 AS rank
FROM items, (SELECT #curRank := 0) r
ORDER BY popularity DESC
This query works perfectly and give me a result with id and rank where the rank value is as expected and respect order by popularity.
What I'm trying to achieve is to set rank value for each entry, and I tried it this way:
UPDATE items A
JOIN (
SELECT id,
#curRank := #curRank + 1 AS rank
FROM items,
(SELECT #curRank := 0) r
ORDER BY popularity DESC
) AS ranks
SET A.rank = ranks.rank
WHERE A.id = ranks.id
A rank value is set for each row but doesn't respect the ORDER BY popularity DESC. Instead rank value seems to be set by an id order (id 1 has rank 1, id 2 has rank 2 etc...).
What am I doing wrong?
Regards,
I think you're making this harder than it should be.
SET #curRank = 0;
UPDATE items
SET rank = (#curRank := #curRank+1)
ORDER BY popularity DESC;
I just set the #curRank variable in a SET statement before the UPDATE. When you try to combine them, it just makes readers of your code wonder what it means.
You don't need to make them part of the same statement. The session variable will keep its value as long as you execute both statements in the same database session.
There's no need for subqueries or joins. Just use UPDATE ... ORDER BY (although UPDATE with ORDER BY doesn't work in MySQL if you do need to do a JOIN).
MySQL has suprising behaviors when dealing with variables and ordering.
One thing that you could try is order earlier, by moving the ORDER BY on items to a a subquery, as follows:
UPDATE items A
JOIN (
SELECT id,
#curRank := #curRank + 1 AS rank
FROM
(SELECT id FROM items ORDER BY popularity DESC) items,
(SELECT #curRank := 0) r
) AS ranks
SET A.rank = ranks.rank
WHERE A.id = ranks.id
Demo on DB Fiddle:
Data:
| id | popularity | rank |
| --- | ---------- | ---- |
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
| 4 | 4 | 4 |
| 5 | 5 | 5 |
After update:
| id | popularity | rank |
| --- | ---------- | ---- |
| 1 | 1 | 5 |
| 2 | 2 | 4 |
| 3 | 3 | 3 |
| 4 | 4 | 2 |
| 5 | 5 | 1 |
Given a Scores table with the following schema
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
I've tried the following:
SET #prev_value = NULL;
SET #rank_count = 0;
SELECT Id, Score, CASE
WHEN #prev_value = Score THEN #rank_count
WHEN #prev_value := Score THEN #rank_count := #rank_count + 1
END AS Rank
FROM Scores
ORDER BY Score
In order to get
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
But instead I get this error:
Line 3: SyntaxError: near 'SET #rank_count = 0;
SELECT Id, Score, CASE
WHEN #prev_value := Score THEN #'
What am I doing incorrectly?
You can count all the distinct scores that are greater than each row's score and add 1 to the result:
select
s.score,
((select count(distinct score) from scores where score > s.score) + 1) rank
from scores s
order by s.score desc
See the demo
In MySQL 8.x you can use the DENSE_RANK() function, as in:
select
Score,
dense_rank() over(order by Score desc) as Rank
from Scores
order by Score desc
You can use variables. I think you just need a single query:
SELECT Id, Score,
(CASE WHEN #prev_value = Score THEN #rank_count
WHEN #prev_value := Score THEN #rank_count := #rank_count + 1
END) AS Rank
FROM (SELECT s.*
FROM Scores s
ORDER BY Score s
) s CROSS JOIN
(SELECT #prev_value := NULL, #rank_count := 0) params;
More recent versions of MySQL require the ordering in a subquery. Of course, the most recent versions offer DENSE_RANK() which is preferable.
I have a table (called users) I need rank of users based on their score but I want rank on the bases of users max score.
+-----------+------------+
| User_id | Score |
+-----------+------------+
| 1 | 12258 |
| 1 | 112 |
| 2 | 9678 |
| 5 | 9678 |
| 3 | 689206 |
| 3 | 1868 |
Expect result
+-----------+------------+---------+
| User_id | Score | Rank |
+-----------+------------+---------+
| 3 | 689206 | 1 |
| 1 | 12258 | 2 |
| 2 | 9678 | 3 |
| 5 | 9678 | 3 |
You are looking for DENSE_RANK, But it supports mysql version higher than 8.0
use correlated-subquery to get max value by each User_id
use two variables one to store rank another to store previous value to make the DENSE_RANK number.
look like this.
CREATE TABLE T(
User_id int,
Score int
);
insert into t values (1,12258);
insert into t values (1,112);
insert into t values (2,9678);
insert into t values (5,9678);
insert into t values (3,689206);
insert into t values (3,1868);
Query 1:
SELECT User_id,Score,Rank
FROM (
SELECT User_id,
Score,
#rank :=IF(#previous = t1.score, #rank, #rank + 1) Rank,
#previous := t1.Score
FROM T t1 CROSS JOIN (SELECT #Rank := 0,#previous := 0) r
WHERE t1.Score =
(
SELECT MAX(Score)
FROM T tt
WHERE t1.User_id = tt.User_id
)
ORDER BY Score desc
) t1
Results:
| User_id | Score | Rank |
|---------|--------|------|
| 3 | 689206 | 1 |
| 1 | 12258 | 2 |
| 2 | 9678 | 3 |
| 5 | 9678 | 3 |
Another trick in MySql 5.7 to calculate a DENSE_RANK (like in MySql 8) is to use a CASE WHEN with the variable assignments in it.
SELECT User_id, MaxScore AS Score,
CASE
WHEN MaxScore = #prevScore THEN #rnk
WHEN #prevScore := MaxScore THEN #rnk := #rnk+1
ELSE #rnk := #rnk+1
END AS Rank
FROM
(
SELECT User_id, MAX(Score) AS MaxScore
FROM YourTable
GROUP BY User_id
ORDER BY MaxScore DESC, User_id
) AS q
CROSS JOIN (SELECT #rnk := 0, #prevScore := null) AS vars
You can test it here on rextester.
Post the problem statement and current code I am using, and wondering if any smart ideas to improve query performance? Using MySQL. Thanks.
Write a SQL query to rank scores. If there is a tie between two scores, both should have the same ranking. Note that after a tie, the next ranking number should be the next consecutive integer value. In other words, there should be no "holes" between ranks.
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
For example, given the above Scores table, your query should generate the following report (order by highest score):
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
SELECT
s.score, scores_and_ranks.rank
FROM
Scores s
JOIN
(
SELECT
score_primary.score, COUNT(DISTINCT score_higher.score) + 1 AS rank
FROM
Scores score_primary
LEFT JOIN Scores score_higher
ON score_higher.score > score_primary.score
GROUP BY score_primary.score
) scores_and_ranks
ON s.score = scores_and_ranks.score
ORDER BY rank ASC;
BTW, post issue from Gordon's code.
BTW, tried sgeddes's code, but met with new issues,
New issue from Gordon's code,
thanks in advance,
Lin
User defined variables are probably faster than what you are doing. However, you need to be careful when using them. In particular, you cannot assign a variable in one expression and use it in another -- I mean, you can, but the expressions can be evaluated in any order so your code may not do what you intend.
So, you need to do all the work in a single expression:
select s.*,
(#rn := if(#s = score, #rn,
if(#s := score, #rn + 1, #rn + 1)
)
) as rank
from scores s cross join
(select #rn := 0, #s := 0) params
order by score desc;
One option is to use user-defined variables:
select score,
#rnk:=if(#prevScore=score,#rnk,#rnk+1) rnk,
#prevScore:=score
from scores
join (select #rnk:=0, #prevScore:=0) t
order by score desc
SQL Fiddle Demo