I have a table like this
+-----+-------+
| pid | score |
+-----+-------+
| 1 | 120|
| 2 | 130|
| 3 | 100|
| 1 | 120|
| 2 | 130|
| 3 | 100|
+-----+-------+
I am trying to get get my users ranking. Currently I am using this query,
SELECT pid
, SUM(score) AS total_score
FROM score
GROUP
BY pid
ORDER
BY total_score DESC
I am getting a sorted list by score with this query but not getting their position. I want something like this
+-----+-------+------+
| pid | score | rank |
+-----+-------+------+
| 2 | 260| 1 |
| 1 | 240| 2 |
| 3 | 200| 3 |
+-----+-------+------+
And can I get this position for a specific user? Like
+-----+-------+------+
| pid | score | rank |
+-----+-------+------+
| 1 | 240| 2 |
+-----+-------+------+
I am using MySQL 5.7.21. Can you please help?
If you have the chance to be in Mysql 8.0.2 or more you have the function rank.
Else You can use a variable to set the rank:
SELECT pid,
total_score,
#curRank := #curRank + 1 AS rank
FROM
(SELECT pid,
sum(score) AS total_score
FROM score
GROUP BY pid
ORDER BY total_score DESC) datas, (SELECT #curRank := 0) r
You can filter after to get only the result for the pid you want
Yes, you can use LIMIT like:
SELECT pid, SUM(score) AS total_score
FROM score
GROUP BY pid
ORDER BY total_score DESC
LIMIT 1
Related
I'm trying to make a query in MySQL that returns me only 10 users from the table, but with a rank value that is the ORDER result of the xp column. Right now I have this:
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM usuarios, (SELECT #curRank := 0) r
ORDER BY xp DESC LIMIT 10;
It looks to be working perfectly when fetching the first 10 users.
+--------------------+------+------+
| id | xp | rank |
+--------------------+------+------+
| 373901344995803138 | 5863 | 1 |
| 701198768049225770 | 5692 | 2 |
| 239203656405221376 | 4961 | 3 |
| 692489002942726154 | 4508 | 4 |
| 416988898628206593 | 3669 | 5 |
| 312003290378534912 | 3155 | 6 |
| 608344569381126167 | 3059 | 7 |
| 671949142473310238 | 3041 | 8 |
| 549743978191519744 | 2991 | 9 |
| 592440479577145383 | 2519 | 10 |
+--------------------+------+------+
But when I try to fetch for example LIMIT 10,10 to get the users between 11 and 20, although they are ordered, their global rank is incorrect because #curRank is not increasing for all the users before the offset.
+--------------------+------+------+
| id | xp | rank |
+--------------------+------+------+
| 638196238436532234 | 1888 | 1 |
| 601269358349516833 | 1447 | 2 |
| 548357514497097743 | 1338 | 3 |
| 203591312031744000 | 1330 | 4 |
| 379034072519016469 | 1283 | 5 |
| 563804445654122497 | 1086 | 6 |
| 421296425981181952 | 1025 | 7 |
| 263816867100098560 | 850 | 8 |
| 631330775379214371 | 776 | 9 |
| 442529076511637504 | 702 | 10 |
+--------------------+------+------+
I don't know a way to make the global ranking work when using LIMIT.
In MySQL 8.0, just use window functions, as demonstrated by Gordon Linoff.
In earlier versions, you basically need a subquery to do what you want. I would recommend:
SELECT *
FROM (
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM (SELECT * FROM usuarios ORDER BY xp DESC) u
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY xp DESC
) t
ORDER BY xp DESC
LIMIT 10, 10;
The subquery ranks all users first, then you can safely filter in the outer query. Note that the query pre-orders the table by xp in a subquery first: this is safer (user variables are tricky in MySQL).
Actually, you don't even needLIMIT in the outer query; you can use a WHERE clause instead:
SELECT *
FROM (
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM (SELECT * FROM usuarios ORDER BY xp DESC) u
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY xp DESC
) t
WHERE rank BETWEEN 11 AND 20
ORDER BY rank
Instead, use row_number():
SELECT id, xp, row_number() over (order by cp desc) as rnk
FROM usuarios
ORDER BY xp DESC
LIMIT 10;
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.
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;
I have a table that holds usernames and results.
When a user insert his results to the DB, I want to execute a query that will return
the top X results ( with their rank in the db) and will also get that user result
and his rank in the DB.
the result should be like this:
1 playername 4500
2 otherplayer 4100
3 anotherone 3900
...
134 current player 140
I have tried a query with union, but then I didnt get the current player rank.
ideas anyone?
The DB is MYSQL.
10x alot and have agreat weekend :)
EDIT
This is what I have tried:
(select substr(first_name,1,10) as first_name, result
FROM top_scores ts
WHERE result_date >= NOW() - INTERVAL 1 DAY
LIMIT 10)
union
(select substr(first_name,1,10) as first_name, result
FROM top_scores ts
where first_name='XXX' and result=3030);
SET X = 0;
SELECT #X:=#X+1 AS rank, username, result
FROM myTable
ORDER BY result DESC
LIMIT 10;
Re your comment:
How about this:
SET X = 0;
SELECT ranked.*
FROM (
SELECT #X:=#X+1 AS rank, username, result
FROM myTable
ORDER BY result DESC
) AS ranked
WHERE ranked.rank <= 10 OR username = 'current';
Based on what I am reading here:
Your table structure is:
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| name | varchar(50) | YES | | NULL | |
| result | int(11) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
Table Data looks like:
+---------+--------+
| name | result |
+---------+--------+
| Player1 | 4500 |
| Player2 | 4100 |
| Player3 | 3900 |
| Player4 | 3800 |
| Player5 | 3700 |
| Player6 | 3600 |
| Player7 | 3500 |
| Player8 | 3400 |
+---------+--------+
You want a result set to look like this:
+------+---------+--------+
| rank | name | result |
+------+---------+--------+
| 1 | Player1 | 4500 |
| 2 | Player2 | 4100 |
| 3 | Player3 | 3900 |
| 4 | Player4 | 3800 |
| 5 | Player5 | 3700 |
| 6 | Player6 | 3600 |
| 7 | Player7 | 3500 |
| 8 | Player8 | 3400 |
+------+---------+--------+
SQL:
set #rank = 0;
select
top_scores.*
from
(select ranks.* from (select #rank:=#rank+1 AS rank, name, result from ranks) ranks) top_scores
where
top_scores.rank <= 5
or (top_scores.result = 3400 and name ='Player8');
That will do what you want it to do
assuming your table has the following columns:
playername
score
calculated_rank
your query should look something like:
select calculated_rank,playername, score
from tablename
order by calculated_rank limit 5
I assume you have PRIMARY KEY on this table. If you don't, just create one. My table structure (because you didn't supply your own) is like this:
id INTEGER
result INTEGER
first_name VARCHAR
SQL query should be like that:
SELECT #i := #i+1 AS position, first_name, result FROM top_scores, (SELECT #i := 0) t ORDER BY result DESC LIMIT 10 UNION
SELECT (SELECT COUNT(id) FROM top_scores t2 WHERE t2.result > t1.result AND t2.id > t1.id) AS position, first_name, result FROM top_scores t1 WHERE id = LAST_INSERT_ID();
I added additional condition into subquery ("AND t2.id > t1.id") to prevent multiple people with same result having same position.
EDIT: If you have some login system, it would be better to save userid with result and get current user result using it.