Users Rating update, recalculation - php

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

Related

what is the best practice leaderboard with php, mysql, memcached?

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.

Highscores (top scores) rank calculation & update query?

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.

Get and order based on most recent entry while in multiple mysql joins

I have the following statement that finds all the players of a team in the current season. The players are ordered by their handicap. If their handicaps are the same they are ordered by the oldest added_date, meaning newer members are lower down the list.
SELECT players.playerid_p,
players.fname,
players.sname,
players.tel,
players.mob,
players.email,
season_players.captain
FROM season_players
LEFT JOIN players ON (season_players.playerid_f = players.playerid_p)
LEFT JOIN handicaps ON (handicaps.playerid_f = players.playerid_p)
WHERE season_players.seasonid_f = '$currentSeason'
AND season_players.teamid_f = '".$row["teamid_p"]."'
GROUP BY players.playerid_p
ORDER BY handicaps.handicap ASC, handicaps.added_date ASC
The handicaps table can have multiple entries per player for any reviews they have had.
I can't figure out how to make the latest handicap to be used for the ordering (something maybe to do with MAX on added_date?) yet at the same time if two or more handicaps are the same it order them by oldest registered first based on added_date.
In stead of trying to figure out how to satisfy your needs in current query, wouldn't it be an option to create a separate table, let's say "Handicaps_latest", which only stores player_id and required info of latest review. The reason for doing this is because you are only trying to get(columns in your select clause) the information about players and nothing really needed from handicaps table. In this case, a handicaps table with multiple entries per player might not be a good table to join. But considering that those data might be required in other logic, so leave them there and create a branch new table only storing latest review data could be an option for your case. But it requires some extra work apparently, that is, whenever you insert a new entry into your original handicpas table, a particular entry in handicaps_latest needs to be updated.
SELECT x.*
FROM my_table x
JOIN
( SELECT id,MAX(other_column) max_other_column FROM my_table GROUP BY id) y
ON y.id = x.id
AND y.max_other_column = x.other_column;

Mysql - How to get a row number after Order by?

Let's say I have a table with the following columns:
p_id
userid
points
Let's say these columns have over 5000 records. So we actually have users with points. Each user has an unique row for their point record. Imagine that every user can get points on the website by clicking somewhere. When they click I update the database with the points they get.
So we have a table with over 5000 records of people who have points, right? Now I would like to order them by their points (descending), so the user with the most point will be at the top of the page if I run a MySQL query.
I could do that by simply running a query like this:
SELECT `p_id` FROM `point_table` ORDER BY `points` DESC
This query would give me all the records in a descending order by points.
Okay, here my problem comes, now (when it is ordered) I would like to display each user which place are they actually. So I'd like to give each user something like this: "You are 623 of 5374 users". The problem is that I cannot specify that "623" number.
I would like to run a query which is order the table by points it should "search" or count the row number, where their records are and than return that value to me.
Can anyone help me how to build a query for this? It would be a really big help. Thank you.
This answer should work for you:
SET #rank=0;
SELECT #rank:=#rank+1 AS rank, p_id FROM point_table ORDER BY points DESC;
Update: You might also want to consider to calculate the rank when updating the points and saving it to an additional column in the same table. That way you can also select a single user and know his rank. It depends on your use cases what makes more sense and performs better.
Update: The final solution we worked out in the comments looked like this:
SELECT
rank, p_id
FROM
(SELECT
#rank:=#rank+1 AS rank, p_id, userid
FROM
point_table, (SELECT #rank := 0) r
ORDER BY points DESC
) t
WHERE userid = intval($sessionuserid);
Row number after order by
SELECT ( #rank:=#rank + 1) AS rank, m.* from
(
SELECT a.p_id, a.userid
FROM (SELECT #rank := 0) r, point_table a
ORDER BY a.points DESC
) m
For some reason the accepted answer doesn't work for me properly - it completely ignores "ORDER BY" statement, sorting by id (primary key)
What I did instead is:
SET #rn=0;
CREATE TEMPORARY TABLE tmp SELECT * FROM point_table ORDER BY points DESC;
SELECT #rn:=#rn+1 AS rank, tmp.* FROM tmp;
Add a new column for position to the table. Run a cron job regularly which gets all the table rows ordered by points and then update the table with the positions in a while loop.

Creating a numerical order index on a MySQL table

I have a table with (essentially) 3 columns - name, votes and rank. I want to order the table by votes and then update 'rank' to reflect this order, so that the one with the most votes will have rank set to 1, the second most votes to 2 etc.
I can do this in PHP but it seems pretty wasteful - is there a way to do this with one SQL query without having to manually process every record in PHP?
Assuming you don't care about ties you can just calculate the rank dynamically in your queries instead of storing it.
SET #rank=0;
SELECT #rank:=#rank+1 AS rank, name, votes
FROM yourTable
ORDER BY votes DESC;
Although #johnfx gave you the correct answer I think you will be interested reading the comments of this article - http://arjen-lentz.livejournal.com/55083.html where various ranking problems are solved.

Categories