MySQL leaderboard with rank - mysql

I have a leaderboard where i want to give each row a rank for how many points they have. This is the code i have so far. It works fine but when two players have the same amount of points, the rank is the same, for example if there are 2/2 players in the DB where they both have 100 points, both their ranks are 1.
I would like to even if they have same amount of points to give one of them a higher rank, so it would be 1,2.
SELECT id, leaderfirst.pictureid, leaderfirst.point, FIND_IN_SET( leaderfirst.point, (
SELECT GROUP_CONCAT( leaderfirst.point
ORDER BY leaderfirst.point DESC )
FROM leaderfirst )
) AS rank
FROM leaderfirst

leaderboard =
SELECT id,
leaderfirst.pictureid,
leaderfirst.point,
FIND_IN_SET( leaderfirst.point,
( SELECT GROUP_CONCAT(
leaderfirst.point ORDER BY leaderfirst.point DESC ,id)
FROM leaderfirst ) ) AS rank
FROM leaderfirst
Try this.

What you want here is an increment counter in the SQL:
There are two ways of doing this:
First:
set #rownum := 0;
And then
SELECT id, leaderfirst.pictureid, leaderfirst.point,
FIND_IN_SET( leaderfirst.point,
( SELECT GROUP_CONCAT(
leaderfirst.point ORDER BY leaderfirst.point DESC
)
FROM leaderfirst )
) AS rank,
#rownum := #rownum + 1 AS row_number
FROM leaderfirst
ORDER BY row_number
Or:
Combining the first two queries together you can use :
SELECT id, leaderfirst.pictureid, leaderfirst.point,
FIND_IN_SET( leaderfirst.point,
( SELECT GROUP_CONCAT(
leaderfirst.point ORDER BY leaderfirst.point DESC
)
FROM leaderfirst )
) AS rank,
#rownum := #rownum + 1 AS row_number
FROM leaderfirst
CROSS JOIN (select #rownum := 0) r
ORDER BY row_number
Source: This answer here.
There are also various variaton on this theme.
Solution 2:
simply ignore trying to generate this data with your MySQL; you said you don't care about order, simply the auto-increment nature of the resultant ranking; therefore you can do something like this if you're using PHP (and similar work in other languages):
Pseudo-code below:
$mysqlDataOutput = mysql result data from your SQL . "ORDER BY rank DESC";
$counter = 0;
foreach($mysqlDataOutput as $resultRow){
print $counter.") ".resultRow['points']. " -
<img src='".$resultRow['pictureid']."' alt=''>";
$counter++;
}
unset($mysqlDataOutput, $resultRow);
If you want to order the other way around (lowest rank first) then simply invert your SQL ORDER BY and set:
$counter = count($mysqlDataOutput);
foreach(){
...
$coounter--;
}

Related

Ranking position without duplicates

I have an issue with a query,
this is my old question that was previously solved
mysql get ranking position grouped with joined table
The problem is that when two players have the same score, the query returns the same ranking position, like 1-1-2-3 ecc. How can I fix this?
In the player's table there are also player_tickets (that is the number of game played) and player_date that is a timestamp.
I thought to get my ranking on base of player_score first, player_tickets then, and finally player_date
This is my older query
SELECT *,
(SELECT 1 + Count(*)
FROM players p2
WHERE p2.`player_game` = p.`player_game`
AND p2.player_score > p.player_score
AND p2.player_status = 0) AS ranking
FROM players p
ORDER BY `player_game`,
player_score DESC
You can simply add more columns to the order by of the window function:
rank() over (
partition by player_game_id
order by player_score desc, player_tickets desc, player_date
) as rank
If you really want to avoid having the same rank twice, you can also use row_number(), which guarantees this - when there are ties, row_number() affects distinct numbers (whose order is hence undefined).
Just add the ranking criteria to your WHERE clause:
SELECT *,
(
SELECT 1 + COUNT(*)
FROM players p2
WHERE p2.player_game = p.player_game
AND
(
(p2.player_score > p.player_score) OR
(p2.player_score = p.player_score AND p2.player_tickets > p.player_tickets) OR
(p2.player_score = p.player_score AND p2.player_tickets = p.player_tickets AND p2.player_date > p.player_date) OR
(p2.player_score = p.player_score AND p2.player_tickets = p.player_tickets AND p2.player_date = p.player_date AND p2.player_id > p.player_id)
)
) AS ranking
FROM players p
ORDER BY player_game, player_score DESC;
You can add additional comparisons in the subquery. Or more easily, use variables:
select p.*,
#rn := if(#g = player_game, #rn + 1,
if(#g := player_game, 1, 1)
) as ranking
from (select p.*
from players p
order by player_game, player_score desc, player_tickets desc, player_date desc
) p cross join
(select #rn := 0, #g := 0) as seqnum;
In newer versions, you would just use row_number() if you don't want ties.

Trying to put a variable into limit to find a median

I trying to use mysql to solve the following solutions:
https://www.hackerrank.com/challenges/weather-observation-station-20/problem
Understanding that a variable cannot be put into LIMIT statement (from this )
My approach>
to declare a new variable to record rowIDs, and use rowID to retrieve the record in the middle.
However, it seems that rowID is not working well.
Could anyone give me some advises?
SELECT ROUND(COUNT(LAT_N)/2,0) FROM STATION into #count;
SELECT ROUND(a.LAT_N,4) FROM (
SELECT *,#row := #row + 1 FROM STATION s, (SELECT #row := 0) r
WHERE #row <=#count
ORDER BY s.LAT_N ASC) a
ORDER BY a.LAT_N DESC LIMIT 1;`
If you are running MySQL 8.0, this is simpler done with window functions:
select round(avg(lat_n), 4) median_lat_n
from (
select s.*, row_number() over(orer by lat_n) rn
from station s
where lat_n is not null
) s
where rn * 2 in (rn - 1, rn, rn + 1)
In earlier versions, variables make it bit tricky; we need one more level of nesting to make it safe:
select round(avg(lat_n), 2) median_lat_n
from (
select s.*, #rn := #rn + 1 rn
from (select * from station order by lat_n) s
cross join (select #rn := 0) p
) s
where rn * 2 in (rn - 1, rn, rn + 1)
The logic is as follows: first enumerate the rows, ordered by lat_n. If the row count is uneven, we pick the middle row; if it is even, we take the average of the two middle values.

What would the equivalent statement be in MySQL 5.6.39?

I recently moved to a shared host that has MySQL 5.6.39 instead of MariaDB 10.x, I was wondering what would the equivalent of the following MariaDB statement in MySQL?
SELECT rank,
total
FROM
(SELECT ROW_NUMBER() OVER (
ORDER BY `prestige` DESC, `xp` DESC) AS rank,
(SELECT COUNT(*)
FROM Modular_LS) AS total,
steamid
FROM Modular_LS) sub
WHERE sub.steamid = '%s'
I got as far as this, but now I'm stuck
SELECT rank, total FROM
(SELECT #rank := #rank +1 as rank FROM Modular_LS,
(SELECT COUNT(*) FROM Modular_LS) AS total, steamid FROM Modular_LS) sub,
(SELECT #rank := 0) r ORDER BY `prestige` DESC, `xp` DESC) t;
The table structure contains the column steamid, xp, prestige
My goal is to order by prestige descending first and then xp descending to put it in a ranking like-order, then using WHERE query to find a specific player's ranking. The output of which contains the rank (position) and the total (total amount of records)
Maybe this will get you started:
SELECT #rank := IF(player_id = #prev, #rank + 1, 1), #prev := player_id
FROM ( SELECT #rank := 1, #prev = 0 ) AS init
JOIN ( SELECT player_id
FROM Modular_LS
ORDER BY prestige DESC, SP DESC
) AS x ;
After a few hours, this is what I came up with that solved my problem.
SELECT
sub.rank
,sub.total
FROM
(
SELECT
t.id
,t.steamid
,#rownum : = #rownum + 1 AS rank
,(
SELECT
COUNT (*)
FROM
Modular_LS
) AS total
FROM
Modular_LS t JOIN (
SELECT
#rownum : = 0
) r
ORDER BY
t.prestige DESC
,t.xp DESC
) sub
WHERE
sub.steamid = '%s'

How to get ranking of specific player?

I have a list of players. The players are sorted by points. What I'd like to know is how do I get the ranking number of a CERTAIN player?
This is my code so far (which doesn't work because it seems to have some bugs):
$rank = mysql_query (SET #rank := 0;
SELECT *, #rank := #rank + 1
FROM ava_users
WHERE id = '".$id."'
ORDER BY points DESC);
$rank_res = mysql_fetch_array($rank);
When I try to use my query I get an error message:
mysql_fetch_array() expects parameter 1 to be resource, boolean given in /Users/***/Documents/Arcades/Arc_development/arc_projects/***/arc_dev_website/arc_offline/includes/profile/profile_main.inc.php
$rank = mysql_query (
"SELECT a.*,
(
SELECT COUNT(1)
FROM ava_users b
WHERE (b.points, b.id) >= (a.points, a.id)
) AS rank
FROM ava_users a
WHERE a.`user` = '$id'"
);
Try this:
SELECT `user`, rank
FROM (
SELECT `user`, ( #rank := #rank + 1 ) as rank
FROM ava_users, ( select (#rank := 0 ) ) rnk
ORDER BY points DESC
) ranks
WHERE `user` = '".$id."'
user is a key word, therefore use user in order to check parameters equality.
Also, mysql_query can only execute 1 query at a time.

Rank in MySQL table

I have a MySQL table called "MyTable" and it basically lists usernames and points (two columns, name and points). I want to say something like "what is joe1928's rank?", which of course is based off his points. How could I do this in MySQL without having to download all that data and sort it and determine the rank myself?
The person with the highest number of points would be ranked 1.
Try getting the number of people with a higher score than your user:
select count(*) from MyTable where score > (select score from MyTable where user = 'Joe');
That will return 0 for the top user.
This page seems to describe and solve your problem.
Notes from that page:
SET #rownum := 0;
SELECT rank, correct FROM (
SELECT #rownum := #rownum + 1 AS rank, correct, uid
FROM quiz_user ORDER BY correct DESC
) as result WHERE uid=xxxxxxxx
SELECT #r AS Rank
FROM MyTable u, (SELECT #r := 0)
WHERE (#r := #r + 1) * (u.Username = 'joe1928')
ORDER BY u.Score DESC
LIMIT 1
select * from [TABLENAME] where [USERNAME] = blah order by [POINTS] desc limit 1;
Based on the link posted by #Dave your query will look like something below:
select Rank,name from
(select #rownum:=#rownum+1 AS 'Rank', p.name
from calls p, (select #rownum:=0) r
order by p.points desc) as rankResults
where name = 'joe';
This is from another stack overflow page, seems to solve your problem.
SELECT uo.*,
(
SELECT COUNT(*)
FROM users ui
WHERE (ui.points, ui.id) >= (uo.points, uo.id)
) AS rank
FROM users uo
WHERE id = #id