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.
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 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.
So I have a table named db_points.
----------------------------------------
| Name | Points | SPoints | RPoints |
----------------------------------------
| Max | 240 | 50 | 1242 |
| Alvin | 600 | 123 | 3012 |
| Amanda | 234 | 1000 | 132 |
| Angela | 50 | 514 | 4023 |
| Rudolph | 2000 | 230 | 1232 |
----------------------------------------
I need the rank based on ordering by TPoints (Points+SPoints), Points, SPoints, the rank based on RPoints and RPoints.
This would look like the following for Angela, Rudolph or Amanda.
--------------------------------------------------------------------
| RankT | Name | TPoints | Points | SPoints | RankR | RPoints |
--------------------------------------------------------------------
| 4 | Angela | 564 | 50 | 514 | 5 | 4023 |
| 1 | Rudolph | 2230 | 2000 | 230 | 2 | 1232 |
| 2 | Amanda | 1234 | 234 | 1000 | 1 | 132 |
--------------------------------------------------------------------
As you see the rankings are different. The higher TPoints, the better RankT; the lower RPoints, the better RankR.
However I would need to get this in one SQL-query. This is what I got so far:
select Rank,
Name,
TPoints,
Points,
SPoints
RankR
from (select (#pos := #pos+1) pos,
(#rank := IF(#prev = TPoints,#rank,#pos)) Rank,
TPoints,
Points,
SPoints,
Name,
RPoints,
select #rownum:=#rownum + 1 as RankR,t.*
from (select (#prev := SPoints+Points) TPoints,
SPoints,
Points,
Name,
RPoints
from db_points
order by TPoints desc)
as ll,
(select RPoints,
Name
from db_points where RPoints > 0 order by RPoints ASC) t,(SELECT #rownum := 0)
r)
as l
where Name = '%s';
Hope that is understandable.
So far I have two working querys I need to put in one
select Rank, Name, TPoints, Points, SPoints from (select (#pos := #pos+1) pos, (#rank := IF(#prev = TPoints,#rank,#pos)) Rank, TPoints, Points, SPoints, Name from (select (#prev := SPoints+Points) TPoints, SPoints, Points, Name from db_points order by TPoints desc) as ll) as l where Name = '%s';
And this one
select RankR
from (select #rownum:=#rownum + 1 as RankR,t.*
from (select RPoints,
Name
from db_points where RPoints > 0 order by RPoints ASC) t,(SELECT #rownum := 0)
r)
a
where Name='%s';
Hope you can read the code, im bad in formating mysql code.
please give this a try
SELECT #rankT:=#rankT+1 as RankT,
T.*
FROM
(SELECT Name,
(Points+Spoints) as TPoints,
Points,
Spoints,
#rankR := #rankR + 1 as RankR,
Rpoints
FROM db_points p, (SELECT #rankR:=0)initialR
ORDER BY Rpoints ASC
)T,(SELECT #rankT:=0)intialT
ORDER BY TPoints DESC
sqlfiddle
Then you can put this whole query as a subquery and do your outer select to look for WHERE name like '%s'
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;
My table:
user | score | category
-----------------------
1 | 20 | 1
2 | 30 | 1
4 | 30 | 1
1 | 20 | 2
2 | 30 | 2
4 | 30 | 3
Expected result:
user | score | category | rank
------------------------------
1 | 20 | 1 | 3
2 | 30 | 1 | 1
4 | 30 | 1 | 1
1 | 20 | 2 | 2
2 | 30 | 2 | 1
4 | 30 | 3 | 1
I need to make a permanent change to the table (not only select this value) so that I could select users like:
SELECT `user`, `rank`, `score` FROM `user` WHERE `category` = 2
My real table is 200k+ rows and has much more columns. It has to be fast.
You can consider an alternative (which you may find to be faster) to a correlated subquery approach which involves using session variables
SET #n := 0, #r := 0, #c := NULL, #s := NULL;
UPDATE users u JOIN
(
SELECT user, score, category,
#n := IF(#c = category, #n + 1, 1) rnum,
#r := IF(#c = category, IF(#s = score, #r, #n), #n) rank,
#c := category, #s := score
FROM users
ORDER BY category, score DESC
) r
ON u.category = r.category
AND u.user = r.user
AND u.score = r.score
SET u.rank = r.rank;
Outcome:
| USER | SCORE | CATEGORY | RANK |
|------|-------|----------|------|
| 2 | 30 | 1 | 1 |
| 4 | 30 | 1 | 1 |
| 1 | 20 | 1 | 3 |
| 2 | 30 | 2 | 1 |
| 1 | 20 | 2 | 2 |
| 4 | 30 | 3 | 1 |
Here is SQLFiddle demo
Here is the query to get the rank:
select t.*,
(select count(*) + 1
from MyTable2 t2
where t2.category = t.category and
t2.score > t.score
) as rank
from MyTable t;
You can put this into an update, by joining again back to the original table, assuming the user, category is unique:
update MyTable join
(select t.*,
(select count(*) + 1
from MyTable2 t2
where t2.category = t.category and
t2.score > t.score
) as rank
from MyTable t
) toupdate
on MyTable.user = toupdate.user and Mytable.category = toupdate.category
set Mytable.rank = toupdate.rank;
A minor variant on peterm's solution. And I don't advocate UPDATE unless you're sure that this table is read only...
SELECT category
, user
, score
, #pcat := #ccat
, #pscore := #cscore
, #ccat := category
, #cscore := score
, #rank := IF(#pcat = #ccat,IF(#pscore = #cscore, #rank, #rank+1),#rank:=1) rank
FROM my_table
JOIN (SELECT #ccat :=null,#pcat := null,#cscore := null, #pscore := null, #rank := 0) sel1
ORDER
BY category,score DESC;
or something like that. I think this will perform significantly faster than Gordon's elegant solution.