There is no reason to reinvent the wheel , and you run the risk of having a buggy, suboptimal code. Your problem is the trivial spread of a common group line problem . There are already proven and optimized solutions to solve this problem , and from this resource I would recommend choosing from the following two solutions. These queries contain the last 30 entries for each player (rewritten for your tables):
select user_id, points from players where ( select count(*) from players as p where p.user_id = players.user_id and p.player_id >= players.player_id ) <= 30;
(Just to make sure I understand your structure: I believe that player_id is a unique key in the players table and that one user can be present in this table as several players.)
The second proven and optimized solution is to use MySQL variables:
set @num := 0, @user_id := -1; select user_id, points, @num := if(@user_id = user_id, @num + 1, 1) as row_number, @user_id := user_id as dummy from players force index(user_id) /* optimization */ group by user_id, points, player_id /* player_id should be necessary here */ having row_number <= 30;
The first query will not be so optimistic (is quadratic), while the second query is optimal (single-pass), but will only work in MySQL. The choice is yours. If you go to the second technique, beware and check it correctly using your keys and database settings; they suggest in some cases that it may stop working .
Your last request is trivial:
select user_id, avg(points) from ( /* here goes one of the above solutions; the "set" commands should go before this big query */ ) as t group by user_id
Please note that I did not include the condition that you have in the 1st request (points != 0) , since I do not understand your requirement well (you did not describe it), and I also think that this answer should be enough common to help others with a similar problem.