Recently I have developed mobile game (LAMP + memcached).
The game has player's score table. The table has member_id, name, score column.
I want to show leaderboard (global rank) to our user.
Just query SELECT * FROM score ORDER BY score DESC, and show the resultset.
But I don't think it is good way. If users reach 20 million, this query seem to be terrible. What is the best practice in this case? Could you give me some advice?
And how can I check specific user's rank? I want to show each user rank where they are.
Well, you don't want to display millions of users on the leader board do you?
SELECT * FROM score ORDER BY score DESC LIMIT 10
Would select top 10
Further, if you want to display a users rank on for instance a profile page this should do:
SELECT COUNT(*) + 1 AS rank FROM score WHERE score > (SELECT score FROM score WHERE member_id = :member_id);
You simply add the current users member_id to the last query and it will count all rows ahead of him and add 1 to that.
Related
I know this has been covered somewhat but it hasn't helped my situation, rather there's been discrepancies in the rank. Wondering if anyone would be able to help!
So i have a game, the database tables are:
users, maps, nicknames, user_game_scores
Im developing a leader board and easily able to get the information ordered by score. fantastic.
But i want to rank this so that i can pull a specific users scores and the rank be relevant to all scores. eg:
GLOBAL SCORES
user info - Score - (rank)1
user info - Score - (rank)2
user info - Score - (rank)3
etc.
Whereas USER SCORES are more likely to be:
user info - Score - (rank)82
user info - Score - (rank)94
user info - Score - (rank)115
etc.
I imagine the implementation to be this:
SELECT users.first_name, users.surname, player_nicknames.nickname, maps.map_name, user_game_scores.score,
FIND_IN_SET( score, ( SELECT GROUP_CONCAT( score ORDER BY score DESC ) FROM user_game_scores ) ) AS rank
FROM `user_game_scores`
INNER JOIN users ON user_game_scores.user_id = users.user_id
INNER JOIN maps ON user_game_scores.map_id = maps.map_id
INNER JOIN player_nicknames ON user_game_scores.user_id = player_nicknames.user_id
WHERE user_game_scores.deleted is null
AND users.deleted is null
AND player_nicknames.deleted is null
ORDER BY user_game_scores.score DESC
But it returns this: (click here) - names etc have been removed from the image as it may not be appropriate to display
As you can see the Rank tends to miss a number or two (number 2 and 23). i understand that something like rank 24 will group and continue (which i prefer to happen in that instance) but i dont understand why some of the rank is missing and really dont want to post process this functionality.
Sorry this is long but i thought id provide as much information as i can. Thanks in advance!
It's probably because your SELECT GROUP_CONCAT subquery doesn't filter "deleted" (deleted is null) entries. – Paul Spiegel 9 hours ago
I am looking for an SQL statement that returns users rank by score, but does so in order, which is not happening with the code below:
$sql = "SELECT COUNT(*) AS rank FROM users WHERE points>=(SELECT points FROM users WHERE uid='$uid')";
Instead, each user has the same rank depending on how many users there are. So if there are 25 users, each of their ranks will display as 25 but this is not actually the case, as surely there must be an individual rank for each user.
I have tried a couple of different of different solutions, but I can't quite put the logic together. I thought this would work:
$sql ="SELECT COUNT(*)-1 AS rank FROM users WHERE points>=(SELECT points FROM users WHERE uid='$uid' ORDER BY DESC)";
But I've had no success.
TLDR; How could I change my SQL statement to reflect users rank in order by score, without multiple users having the same rank?
If you are trying to order the data in the table using the points and give them individual rank you can use ROW_NUMBER()
SELECT *,ROW_NUMBER() OVER (ORDER BY points DESC) AS RANK FROM users
ROW_NUMBER() is not available in MYSQL. You can do following for MYSQL
SELECT
#r:=#r+1 Rank,tbl.*
FROM users tbl, (select #r:=0) as r
ORDER BY users.points DESC;
Consider you have data
Points
3
7
8
Using Select statement with ROW_NUMBER() you will get result as follows
Result:
Points Rank
8 1
7 2
3 3
I have a high scoring (top scores) system, which is calculating positions by players's eperience.
But now I need to use the player's rank in other places just the web, maybe more places in the web too like personal
high scores, and it will show the player's rank in that skill.
Therefore just looping & playing with the loop cycle like rank++ won't really work, cause I need to save that rank for
other places.
What I could do is loop through all players and then send a query to update that player's rank, but what if i have 1000 players? or more?
that means 1000 queries per load.
I have thought if there could be a SQL query I can use to do the same action, in one or two queries.
How can I do this? I calculate ranks by ordering by player's eperience, so my table structure looks like this:
Tables:
Players
id (auto_increment) integer(255)
displayname varchar(255) unique
rank integer(255) default null
experience bigint(255)
This should give you the rank for user with id = 1. If you want every player, just remove the WHERE clause:
SELECT a.id, a.displayname, a.rank, a.experience
FROM (
SELECT id, displayname, #r:=#r+1 AS rank, experience
FROM players, (SELECT #rank:=0) tmp
ORDER BY experience DESC) a
WHERE a.id = 1
I wouldn't have rank in the players table directly, since this would mean that you would have to recalculate it every time a user changes experience. You could do this query anytime you want to get the rank for a player or for a leaderboard.
If you still want to update it, You can do an INNER JOIN with this query to UPDATE the original table with the rank from this query.
I have a table with fields id, votes(for each users), rating.
Task: Counting user rating based on votes for him and for others. that is, each time i update the field votes needed recalculation field rating.
Which means some can be on the 3rd place. voted for him and that he would be stood up to 2rd place, and the other vice versa - from 2 to 3. (in rating fiels)
How to solve this problem? Each time update the field to count users ratings on php and do a lot of update query in mysql is very expensive.
If you want to get the ratings with a select without having a rating column, then this is the way. However from a performance perspective I cannot guarantee this will be your best option. The way it works is that if two users have the same amount of votes they will have the same rating and then it will skip ahead the necessary number for the next different rating:
set #rating:=0;
set #count:=1;
select id,
case when #votes<>votes then #rating:=#rating+#count
else #rating end as rating,
case when #votes=votes then #count:=#count+1
else #count:=1 end as count,
#votes:=votes as votes
from t1
order by votes desc
sqlfiddle
This gives you an extra column which you can ignore, or you could wrap this select in to a subquery and have:
select t2.id,t2.votes,t2.rating from (
select id,
case when #votes<>votes then #rating:=#rating+#count
else #rating end as rating,
case when #votes=votes then #count:=#count+1
else #count:=1 end as count,
#votes:=votes as votes
from t1
order by votes desc) as t2
but the sqlfiddle is strangely giving inconsistent results so you'd have to do some testing. If anyone knows why this is I'd be interested in knowing the reason.
If you want to get the rating for just one user then doing the subquery option and using a where after the from should give you the desired result. sqlfiddle - but again, inconsistent results, run it a few times and sometimes it gives rating as 10 other times as 30. I think testing in your db to see what happens will be best.
Well it depends on a lot of factors
Do you have a large system that is growing exponentially?
Do you require the voting data for historical reporting?
Do users need to register when they vote?
Will this system be use only for one voting type throughout the system life cycle or will more voting on different subjects take place?
If all of the answers are NO then your current update method will work just fine. Just ensure that you apply best coding and MySQL table practices anyway.
Let assume most or all your answers were YES then I would suggest the following:
Every time a vote takes place INSERT the record into your table
Using INSERT, add a timestamp, user id if not possible then maybe an ip address/location
Assign a subject id as foreign key from the vote_subject table. In this table store the subject and date of voting
Now you can create a SELECT statement that can count the votes and calculate the ratings. The person top of the vote count list will get rating 1 in the SELECT. Furthermore you can filter per subject, per day, per user and you should also be able to determine volume depending on the result required.
All this of course dependent on how your system will scale in future. This might be way overkill but something to think about.
Yes aggregations are expensive. You could update a rank table every five minutes or so and query from there. The query as you probably already now is this:
select id, count(*) as votes
from users
group by id
order by votes desc
Instead of having the fields id, votes and rating, alter the table to have the fields id, rating_sum and rating_count. Each time you have a new rating you quering the database like this:
"UPDATE `ratings` SET `rating_count` = `rating_count` + 1, `rating_sum` = `rating_sum`+ $user_rating WHERE `id` = $id"
Now the rating is just the average -> rating_sum / rating_count. No need to have a field with the rating.
Also, to prevent a user rate more than one times, you could create a table named rating_users that will have 2 foreign keys the users.id and ratings.id. The primary key will be (users.id, ratings.id). So each time a user tries to rate first you check this table.
I would recommend doing this when querying the data. It would be much simpler. Order by votes descending.
Perhaps create a view and use the view when querying the data.
You could try something like this:
SET #rank := 0
select id, count(*) as votes, #rank := #rank + 1
from users
group by id
order by votes desc
Or
SET #rank := 0
select id, votes, #rank := #rank + 1
from users
order by votes desc
I'm using a MySQL database to store scores for my game. A very simplified version of the table would be
(PlayerID - int)
(Name - string)
(Score - int)
I'd like to form a query that would return me a set of say 10 results where the player of interest is in the middle of the table.
Perhaps an example would make it more clear.
I have just achieved a high score, my name is Steve. When I look at the high score table I'd like to see the 5 scores below me and the 5 scores above me. Obviously if I have the top score I will see the 9 scores below me, and conversely, if I am at the bottom I will see the 9 scores above me. The score table could consist of thousands of scores.
As the database is specifically designed for querying sets of data I'd like to reduce the amount of post processing on the results.
Does anyone have an idea for a query to achieve this?
Thanks
Rich
if you have the user id and score (userId and userScore) you can do:
SELECT * FROM Scores
WHERE PlayerID = userId
OR PlayerID IN (SELECT PlayerID FROM Scores WHERE Score > userScore ORDER BY Score LIMIT 5)
OR PlayerID IN (SELECT PlayerID FROM Scores WHERE Score < userScore ORDER BY Score DESC LIMIT 5)
ORDER BY Score
EDIT: following your comments:
First of all, you need to know the rank of the current user. You can know it with such a query as SELECT COUNT(*) FROM Scores WHERE Score > userScore
It gives you a number n
Then you compute two numbers usersUp and usersDown such as usersDown + userUp = 10 and n - userUp>0 and n + usersDown < SELECT COUNT(*) FROM Scores
Then you can use a UNION instead of IN and Subqueries:
SELECT * FROM Scores WHERE PlayerID = userId
UNION
SELECT * FROM Scores WHERE Score > userScore ORDER BY Score LIMIT usersUp
UNION
(SELECT * FROM Scores WHERE Score < userScore ORDER BY Score LIMIT usersDown)
if you want to stay on the mysql server side you can gather all that in a stored procedure