While making a game the MySQL call to get the top 10 is as follows:
SELECT username, score FROM userinfo ORDER BY score DESC LIMIT 10
This seems to work decently enough, but when paired with a call to get a individual player's rank the numbers may be different if the player has a tied score with other players. The call to get the players rank is as follows:
SELECT (SELECT COUNT(*) FROM userinfo ui WHERE (ui.score, ui.username) >= (uo.score, uo.username)) AS rank FROM userinfo uo WHERE username='boddie';
Example results from first call:
+------------+-------+
| username | score |
+------------+-------+
| admin | 4878 |
| test3 | 3456 |
| char | 785 |
| test2 | 456 |
| test1 | 253 |
| test4 | 78 |
| test7 | 0 |
| boddie | 0 |
| Lz | 0 |
| char1 | 0 |
+------------+-------+
Example results from second call
+------+
| rank |
+------+
| 10 |
+------+
As can be seen, the first call ranks the player at number 8 on the list, but the second call puts him at number 10. What changes or what can I do to make these calls give matching results?
Thank you in advance for any help!
You need in the first query to :
ORDER BY
score DESC,
username DESC
This way it will reach at rank 10... this is due to the username comparison in the second query :
(ui.score, ui.username) >= (uo.score, uo.username)
I would change your Order by to include username, so that you always get the same order. So it would look like:
... ORDER BY score DESC, username ASC ...
Another way of doing it:-
SELECT UsersRank
FROM
(
SELECT userinfo.score, userinfo.username, #Rank:=#Rank+1 AS UsersRank
FROM userinfo
CROSS JOIN (SELECT (#Rank:=0)) Sub1
ORDER BY userinfo.score DESC, userinfo.username
) Sub2
WHERE username = 'boddie'
SELECT
uo.username,
(SELECT COUNT(*)+1 FROM userinfo ui WHERE ui.score>uo.score) AS rank
FROM userinfo uo
WHERE uo.username='boddie'
Or if you need to get username, score and ranks:
SELECT
uo.username,
uo.score,
(#row := #row + 1) AS rank
FROM userinfo uo
JOIN (SELECT #row := 0) r
ORDER BY uo.score DESC, uo.username ASC
You could add
HAVING uo.username = 'boddie'
to get only one user
Try this.
SELECT (COUNT(*)+1) AS rank
FROM userinfo ui
WHERE ui.score > (SELECT score
FROM userinfo
WHERE username='boddie');
Related
It's been asked before, but I can't get it to work properly. The selected answer doesn't work with duplicate values. The second answer should be able to handle duplicates according to the poster, but it's not functioning correctly with my data.
What I want to achieve is pretty simple:
I have a database containing all scores of all users. I want to build a highscore table, so I want to select all highscore rows of each user. With highscore row I mean the row for that user where his score is the highest.
Here's a demo I made based on the answer I mentioned at the top:
CREATE TABLE test(
score INTEGER,
user_id INTEGER,
info INTEGER
);
insert into test(score, user_id, info)
values
(1000, 1, 1),
(1000, 1, 2),
(2000, 2, 3),
(2001, 2, 1);
--
SELECT t.*
FROM test t
JOIN (SELECT test.user_id, max(score) as mi FROM test GROUP BY user_id) j ON
t.score = j.mi AND
t.user_id = j.user_id
ORDER BY score DESC, info ASC;
Expected output:
+-------+---------+------+
| score | user_id | info |
+-------+---------+------+
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
+-------+---------+------+
--> every user_id is present with the row where the user had the highest score value.
Real output:
+-------+---------+------+
| score | user_id | info |
+-------+---------+------+
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
| 1000 | 1 | 2 |
+-------+---------+------+
--> when there are duplicate values, user show up multiple times.
Anyone who can point me in the right direction?
I assume when there are duplicate scores you want the lowest info just like your expected output.
With NOT EXISTS:
select t.* from test t
where not exists (
select 1 from test
where user_id = t.user_id and (
score > t.score or (score = t.score and info < t.info)
)
);
See the demo.
For MySql 8.0+ you can use ROW_NUMBER():
select t.score, t.user_id, t.info
from (
select *, row_number() over (partition by user_id order by score desc, info asc) rn
from test
) t
where t.rn = 1
See the demo.
Results:
| score | user_id | info |
| ----- | ------- | ---- |
| 1000 | 1 | 1 |
| 2001 | 2 | 1 |
If the combination of (user_id, info) is UNIQUE and NOT NULL (or PRIMARY KEY), then you can use a LIMIT 1 subquery in the WHERE clause:
SELECT t.*
FROM test t
WHERE (t.score, t.info) = (
SELECT t2.score, t2.info
FROM test t2
WHERE t2.user_id = t.user_id
ORDER BY t2.score DESC, t2.info ASC
LIMIT 1
)
ORDER BY t.score DESC, t.info ASC;
The result will be:
| score | user_id | info |
|-------|---------|------|
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
demo on sqlfiddle
SELECT info FROM test HAVING MAX(score) was used to keep the info field relevant with the row containing the MAX(score).
SELECT MAX(score) score, user_id, (SELECT info FROM test HAVING MAX(score)) AS info FROM test GROUP BY user_id ORDER BY score DESC;
I have a sports database where I want to sort the data by a custom field ('Rating') and update the field ('Ranking') with the row number.
I have tried the following code to sort the data by my custom field 'Rating'. It works when I sort it by a normal field, but not with a custom/calculated field. When the sorting has been done, I want it to update the field 'Ranking' with the row number.
Ie the fighter with the highest 'Rating' should have the value '1' as 'Ranking.
SELECT id,lastname, wins, Round(((avg(indrating)*13) + (avg(Fightrating)*5) * 20) / 2,2) as Rating,
ROW_NUMBER() OVER (ORDER BY 'Rating' DESC) AS num
from fighters
JOIN fights ON fights.fighter1 = fighters.id
GROUP BY id
The code above isn't sorting the Rating accurately. It sorts by row number, but the highest Rating isn't rated as #1. It seems a bit random.
SQL Fiddle: http://sqlfiddle.com/#!9/aa1fca/1 (This example is correctly sorted, but I want it to update the "Ranking" column by row number - meaning the highest rated fighter (by 'Rating') gets '1' in the Ranking column, the second highest reated fighter gets '2' in the Ranking column etc).
Also I would like to be able to add WHERE clause in the fighters table (where fighters.organization = 'UFC') for example.
First, let's fix your query so it runs on MySQL < 8.0. This requires doing the computing and sorting in a subquery, then using a variable to compute the rank:
select
id,
rating,
#rnk := #rnk + 1 ranking
from
(select #rnk := 0) r
cross join (
select
fighter1 id,
round(((avg(indrating)*13) + (avg(fightrating)*5) * 20) / 2,2) as rating
from fights
group by fighter1
order by rating desc
) x
Now we use the update ... join ... set ... syntax to update the fighters table:
update fighters f
inner join (
select
id,
rating,
#rnk := #rnk + 1 ranking
from
(select #rnk := 0) r
cross join (
select
fighter1 id,
round(((avg(indrating)*13) + (avg(fightrating)*5) * 20) / 2,2) as rating
from fights
group by fighter1
order by rating desc
) x
) y on y.id = f.id
set f.ranking = y.ranking;
Demo in a MySQL 5.6 fiddle based on the fiddle you provided in the comments.
The select query returns:
| id | rating | ranking |
| --- | ------ | ------- |
| 3 | 219.5 | 1 |
| 4 | 213 | 2 |
| 1 | 169.5 | 3 |
| 2 | 156.5 | 4 |
And here is the content of the fighters table after the update:
| id | lastname | ranking |
| --- | ---------- | ------- |
| 1 | Gustafsson | 3 |
| 2 | Cyborg | 4 |
| 3 | Jones | 1 |
| 4 | Sonnen | 2 |
I have a table with this data
+------+-----+————-----+
|Props |Score|Type |
+------+-----+-------- +
|1.EY | 30|Core
|2.FG | 29|Core
|2.YUE | 29|Core
|3.VB. | 28|Elective
|4.RX. | 67|Elective
|5.XE. | 89|Elective
|6.TF. | 60|Elective
|7.HK | 76|Elective
|8.ER | 58|Elective
I want to calculate the overall score by adding all Core scores plus any of the three best Elective scores. I cant seem to find my way around it.
Expected Results is 320: 3 Core Score + 3 best Elective
i.e(30+29+29)+(89+76+67)
Use UNION ALL for the 2 cases of scores and then sum over the returned scores:
select sum(t.score) totalscore
from (
select score from tablename
where type = 'Core'
union all
select t.score from (
select score from tablename
where type = 'Elective'
order by score desc
limit 3
) t
) t
See the demo.
Result:
| totalscore |
| ---------- |
| 320 |
You could rank the records by type within type partitions in an inner query and do a conditional sum in the outer query, like:
SELECT
SUM(CASE
WHEN type = 'Core' THEN score
WHEN type = 'Elective' AND rn <= 3 THEN score
ELSE 0
END) res
FROM (
SELECT
t.*,
ROW_NUMBER() OVER(PARTITION BY type ORDER BY score DESC) rn
FROM mytable t
) x
Demo on DB Fiddle with your sample data:
| res |
| --- |
| 320 |
I have the following data structure (simplified):
Users:
ID
Name
Events:
ID
Date
Results:
ID
User ID (foreign key)
Event ID (foreign key)
Points: (int)
I would like to know (ideally the most efficient way):
How to get a user's position in a 'league' compared to other users. And - - If possible using one query (or sub queries), how to break this down by event, e.g. the user's position after the 1st event, 2nd event, 3rd event etc.
I can get the leaderboard with:
select users.name, SUM(results.points) as points
from results
inner join users on results.user_id = users.id
group by users.id
order by points DESC
However, I'd like to know a user's position without having to return the entire table if possible.
Edit: I have supplied some sample data here.
Ideal output:
| User ID | Rank |
| 3 | 1 |
| 1 | 2 |
| 2 | 3 |
and something similar to (not exactly like this, it's flexible, just something that shows the user's rank from each event)
| User ID | After Event | Rank |
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 1 | 3 | 2 |
| 2 | 1 | 2 |
| 2 | 2 | 2 |
| 2 | 3 | 1 |
| 3 | 1 | 3 |
| 3 | 2 | 3 |
| 3 | 3 | 3 |
MySQL 8.0+ supports window functions so the use of dense_rank() comes in handy.
MySQL under 8.0 solution
Since your version is 5.7 you could imitate this like below:
select
t.id,
CASE WHEN #prevRank = points THEN #currRank
WHEN #prevRank := points THEN #currRank := #currRank + 1
END AS rank
from (
select users.id, SUM(results.points) as points
from results
inner join users on results.user_id = users.id
group by users.id
order by points DESC
) t
cross join (SELECT #currRank := 0, #prevRank := NULL) r
If you need data for particular user then add a WHERE condition to filter out everyone else in an outer query:
select *
from (
<< above query here >>
) t
where id = ? -- your id here
MySQL 8.0+ solution
rank is a reserved keyword so backticks are required when naming a column. We're using dense_rank window function which will assign ranks based od descending sorting of points acquired:
select id, dense_rank() over (order by points desc) as `rank`
from (
select users.id, SUM(results.points) as points
from results
inner join users on results.user_id = users.id
group by users.id
) t
order by `rank`
SET #rowno = 0;
select UserID, max(points), #rowno:=#rowno+1 as rank from
(
select users.id as UserID ,users.name as users_name,events.name, SUM(results.points) as points
from results
inner join users on results.user_id = users.id
inner join events on results.event_id= events.id
group by users.id,events.name,users.name
order by points DESC
) as T
group by UserID
order by max(points) desc
I have a table in mySql which has the users ID and scores.
What I would like to do is organise the table by scores (simple) but then find where a certain user ID sits in the table.
So far I would have:
SELECT * FROM table_score
ORDER BY Score DESC
How would I find where userID = '1234' is (i.e entry 10 of 12)
The following query will give you a new column UserRank, which specify the user rank:
SELECT
UserID,
Score,
(#rownum := #rownum + 1) UserRank
FROM table_score, (SELECT #rownum := 0) t
ORDER BY Score DESC;
SQL Fiddle Demo
This will give you something like:
| USERID | SCORE | USERRANK |
-----------------------------
| 4 | 100 | 1 |
| 10 | 70 | 2 |
| 2 | 55 | 3 |
| 1234 | 50 | 4 |
| 1 | 36 | 5 |
| 20 | 33 | 6 |
| 8 | 25 | 7 |
Then you can put this query inside a subquery and filter with a userId to get that user rank. Something like:
SELECT
t.UserRank
FROM
(
SELECT *, (#rownum := #rownum + 1) UserRank
FROM table_score, (SELECT #rownum := 0) t
ORDER BY Score DESC
) t
WHERE userID = '1234';
SQL Fiddle Demo
For a given user id, you can do this with a simple query:
select sum(case when ts.score >= thescore.score then 1 else 0 end) as NumAbove,
count(*) as Total
from table_score ts cross join
(select ts.score from table_score ts where userId = '1234') thescore
If you have indexes on score and userid, this will be quite efficient.