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
Related
I need to calculate the average score of every player's 3 most recent scores (golf rounds). If it matters to the code / syntax, this is only required when they have a minimum of 3 scores.
I have a view that has these fields:
round_id
player_id
score
round_date
As new scores are entered into the database, I would like to keep track and notified. I thought my options would to be keep this within the database (somehow) or to generate appropriate php code to do the equivalent. But thought keeping inside the database itself, it could / would handle new data insertions / updates better. PHP would have a page that would need to be loaded to execute.
I have seen some examples of nested select statements, and some that have mysql variables (my basic sql skills, not really gone into variables so would need explaining). none seem to directly relate to my specific needs.
Thanks
Something like this (untested):
select player_id, avg(substring_index(substring_index(scores,',',round),',',-1))
from
(
select 1 round union all select 2 union all select 3
) last_rounds
cross join
(
select player_id, group_concat(score order by round_date desc) scores
from player_round
group by player_id
having count(*) >= 3
) player_scores
group by player_id
Consider this mysql tables structure (useful to store private/group chat messages):
USERS
user_id
username
password
GROUPS (= DISCUSSIONS / TOPICS)
group_id
name
GROUPS_MEMBERS (= MEMBERS OF A SPECIFIC DISCUSSION / TOPIC)
group_id
user_id
MESSAGES
message_id
timestamp
from_user_id
destination_type (enum - group, user)
destination_id
Can you please help me with the query to retrieve the list of the 5 more recent dicussions (either private or group) in which a specific user has been a active?
Important:
I don't have actual code since I'm just deciding how to structure the database tables. The table structure presented above it's pretty self-explanatory (destination_id is a reference to group_id, and group members are all the users that will receive a message. Finally, all messages sent between the users of a specific group make a discussion or topic).
Here is what I want to do (it's very easy... don't over-think it... it's like any chat/messaging system like Facebook or Gmail etc).
When a user logs in and opens the chat he will of course see all the latest discussion which he is/has been a part of. In a chronological DESC order.
So I need to write the query to retrieve the latest 5 GROUP_IDs (= discussions) in chronological DESC order. But only the discussions which the logged-in user is a part of.(Of course I have the id of the logged-in user.. for example 16)
P.s. I didn't build this table structure myself but it seems logic; the only problem is the one presented above.
Here's my suggestion. You can get different records by using DISTINCT and get only five records by using LIMIT. You can replace logged_in_user_id with the login id.
SELECT DISTINCT GROUPS.group_id FROM USERS
JOIN GROUP_MEMBERS ON USERS.user_id = GROUP_MEMBERS.user_id
JOIN GROUPS ON GROUPS.group_id = GROUP_MEMBERS.group_id
JOIN MESSAGES ON destination_type = 'group' AND destination_id = GROUPS.group_id
WHERE USERS.user_id = logged_in_user_id
ORDER BY timestamp DESC
LIMIT 5;
#Igor Carmagna:
I have gone through your question and according to that i think you are required list the top 5 messages which have been left by the end users right ?. So for that please please follow below given steps.
1) First and for most thing you are required to do is that Join.
2) In this step you are required to use max() function which will give you list of the users on the base of messages received. Now, according to your question you are required to have only top 5 records so you are bound use (max-5) function this will given top 5 records
Hope this will make you day !!
Cheers :) :P
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.
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