tags:

views:

79

answers:

4

I have two tables

User
  id
  name

Result
  id
  user_id
  score
  created_at

Every hour a cron job runs and puts data in the result table for each user - assigning them a score based on some external variable parameters.

So the Result table contains many records for each user.

I want to retreive a 'top ten' users at any given point. So only one row for each user should be returned, and it should contain their most recent available score, and ideally only 10 rows relating to those ten users with the highest scores.

I currently fetch all results and do the leg work with php which I'm fairly sure would be faster and more efficient if handled by the database. But I don't know where to start.

A: 

This should do the job:

SELECT user.id, user.name, MAX(result.score) maxscore FROM user
Left Join result on result.user_id=user.id
GROUP By user.id
ORDER By maxscore DESC
LIMIT 0,10

Second try not sure about it yet...

SELECT user.id, user.name, result.score, result.created_at FROM user
LEFT JOIN result ON result.user_id = user.id
WHERE result.id IN (SELECT r.id FROM result r GROUP By r.user_id ORDER By MAX(r.created_at))
ORDER By score DESC
LIMIT 0,10
Rufinus
There is no need for that to be a left join since he is looking for the top ten scores and a user without a score should not be returned.
Jackson Miller
no, there is no need, but it doesnt hurt either.
Rufinus
This will not give the recent score of the player. It will give the max score of the player.
Damodharan R
wow. that's great. Just tested and it works.I guess the join is good as it means I can use the user.name, where as otherwise I wouldnt. I think.
ah, yeah, ok. It's giving their biggest scores to date, I think.
The idea is it should compare the most recent result for each user, and return the ten users with the highest score out that set of the most recent results.
@Rufinus Where is the date condition (the most recent score) ?
Patrick
damn, ok, this isnt so easy anymore... let me try some things...
Rufinus
@patrick: yes i know, i missunderstand his question.
Rufinus
A: 

This may work:

select u.id, u.name, x.score from user u, (select user_id, sum(score) score from results) x where u.id = x.user_id order by x.score limit 10

El Guapo
You have forget the date condition (most recent score), and you are summing all the score result
Patrick
yes - definately needs just the most recent score, not summed.
+1  A: 

Ok get the last created date for each user and from there choose the distinct user with their score sorted in descendant order and take the 10 first line

SELECT DISTINCT u.id, u.name, r.score, md.max_date
FROM user u
INNER JOIN result r ON (u.id=r.user_id)
INNER JOIN (
 SELECT user_id, MAX(created_at) max_date
 FROM result
 GROUP BY user_id
) md ON (md.user_id=r.user_id AND r.created_at=md.max_date)
ORDER BY r.score DESC
LIMIT 10
Patrick
I've just tried that patrick, and I thought I understood it, but it only returns 1 result, though there are a large number of users and many with scores from the last cron entry.
I have modifiy it is it better ?
Patrick
@patrick: your sql doesnt work on my mysql, it gave a invalid mix of group commands without group by command. but adding a group by in the inner join select leads to a result, but its wrong. (dont know which DB is used by cr0wn3r)
Rufinus
@Rufinus you're right i forget it, just add it
Patrick
GROUP BY id in the inner join is not correct as I think id is the primary key for the result table. It needs to be group by user_id.
Damodharan R
hmm. now that seems to return 10 results, correctly ordered, but all for the same user.
@Damodharan need more sleep ;)
Patrick
thanks Patrick. yes - same result.
+4  A: 
SELECT u.id,
       u.name,
       r.score
FROM Result r
JOIN
  (SELECT user_id, MAX(created_at) AS max_date
   FROM Result
   GROUP BY user_id) lr ON r.user_id=lr.user_id
JOIN User u ON u.id=r.user_id
WHERE r.created_at=lr.max_date
ORDER BY r.score DESC LIMIT 10;

A sample run:

mysql> insert into User (name) values ('foo'), ('bar');
mysql> insert into Result (user_id, score, created_at) values (1,100,'2010-01-20'), (2,150,'2010-01-20'),(1,150,'2010-01-21'),(2,100,'2010-01-21');
mysql> SELECT u.id,        u.name,        r.score FROM Result r JOIN   (SELECT user_id, MAX(created_at) AS max_date    FROM Result    GROUP BY user_id) lr ON r.user_id=lr.user_id JOIN User u ON u.id=r.user_id WHERE r.created_at=lr.max_date ORDER BY r.score DESC LIMIT 10;
+----+------+-------+
| id | name | score |
+----+------+-------+
|  1 | foo  |   150 | 
|  2 | bar  |   100 | 
+----+------+-------+
2 rows in set (0.00 sec)
Damodharan R
confirmed, you nailed it :)
Rufinus
+1 We end with the same statement but i add the DISTINCT clause
Patrick
Caution: This will not work correctly when there can be more than one identical `created_at` for a user. If this cannot logically happen, I would ensure it by creating a unique index on `user_id,created_at`. This would also speed up the query.
Tomalak
:) Nice. Ive put that in and been through the results and it does look right. Thats great. Cheers. Thanks to everyone who helped!
@Tomalak Thanks - shouldnt be a problem, unless soemthing else has gone horribly wrong.