MySQL sum last X rows per group - mysql

I have the following tables:
player_records
int id (pk)
int player_id (fk)
int round_id (fk)
int kills
players
int id (pk)
varchar name
rounds
int id (pk)
int duration // round duration in seconds
I need to select kills/hour (SUM(kills)/SUM(duration/60/60)) ratio for each player from the last 10 rounds they've participated, provided not every player takes part in a round.
All similar questions that I've found point to this article, but I haven't been able to apply its tricks to my needs above as I have to limit the records to 10 before grouping.
To further illustrate it, this is how I'd be doing if I needed just a single player:
SELECT SUM(t1.kills)/SUM(t1.duration)*60*60 kills_hour_last_10
FROM (
SELECT records.kills, rounds.duration
FROM records
JOIN rounds ON rounds.id = records.round_id
WHERE records.player_id = 1
ORDER BY records.id DESC
LIMIT 10
) t1;
Updated with fiddle (Those 3 queries represent the expected results for each player. All I need is to know how to get all of them in the same query).

You can use a rank query mysql does not have window functions for this type of results to get n records per group
SELECT t.player_id,
SUM(t.kills)/SUM(t.duration)*60*60 kills_hour_last_10
FROM (
SELECT *,
#r:= CASE WHEN #g = player_id THEN #r + 1 ELSE 1 END rownum,
#g:= player_id
FROM (
SELECT r.player_id,r.id,r.kills, rs.duration
FROM records r
JOIN rounds rs ON rs.id = r.round_id
ORDER BY r.player_id ASC,r.id DESC
) t1
CROSS JOIN (SELECT #g:=NULL,#r:=NULL) t2
) t
WHERE rownum <= 10
GROUP BY t.player_id
Fiddle Demo
Above query will give ranks to the record which belongs to same player_id group and in parent query you can limit records to 10 for each player_id and the perform your aggregation logic note that ORDER BY r.player_id,r.id DESC is important to order results by player and then its records in descending manner,if you need player names then join players in parent query

Related

Average maximum MYSQL

I want to select the average of the high score for 2-3 players but im not sure how to go about this.
Table Match has (id, player, score)
I would like to get the maximum score for each specified player (by id) and then calculate the average of these returned scores.
Any help would be appreciated
CREATE TABLE Match(Id integer PRIMARY KEY, score integer);
/* Create few records in this table */
INSERT INTO Match VALUES(1,'10');
INSERT INTO Match VALUES(2,'20');
INSERT INTO Match VALUES(3,'60');
INSERT INTO Match VALUES(4,'100');
INSERT INTO Match VALUES(5,'80');
COMMIT;
select m.*,
(select sum(score)
from (select score
from Match s
where s.id = s.id
order by score desc
limit 3
) s2
) as summax3
from Match m
The following query is going to return the highest score of each player:
SELECT *
FROM
(
SELECT `match`.id, `match`.score
FROM `match`
ORDER BY score DESC
LIMIT 3
) AS high_scores
GROUP BY id
ORDER BY score DESC
And for getting the average:
SELECT AVG(score) AS average
FROM
(
SELECT `match`.id, `match`.score
FROM `match`
ORDER BY score DESC
LIMIT 3
) AS high_scores

Select sum of top three scores for each user

I am having trouble writing a query for the following problem. I have tried some existing queries but cannot get the results I need.
I have a results table like this:
userid score timestamp
1 50 5000
1 100 5000
1 400 5000
1 500 5000
2 100 5000
3 1000 4000
The expected output of the query is like this:
userid score
3 1000
1 1000
2 100
I want to select a top list where I have n best scores summed for each user and if there is a draw the user with the lowest timestamp is highest. I really tried to look at all old posts but could not find one that helped me.
Here is what I have tried:
SELECT sum(score) FROM (
SELECT score
FROM results
WHERE userid=1 ORDER BY score DESC LIMIT 3
) as subquery
This gives me the results for one user, but I would like to have one query that fetches all in order.
This is a pretty typical greatest-n-per-group problem. When I see those, I usually use a correlated subquery like this:
SELECT *
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3;
This is not the whole solution, as it only gives you the top three scores for each user in its own row. To get the total, you can use SUM() wrapped around that subquery like this:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3) tmp
GROUP BY userId;
Here is an SQL Fiddle example.
EDIT
Regarding the ordering (which I forgot the first time through), you can just order by totalScore in descending order, and then by MIN(timestamp) in ascending order so that users with the lowest timestamp appears first in the list. Here is the updated query:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score, timeCol
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3) tmp
GROUP BY userId
ORDER BY totalScore DESC, MIN(timeCol) ASC;
and here is an updated Fiddle link.
EDIT 2
As JPW pointed out in the comments, this query will not work if the user has the same score for multiple questions. To settle this, you can add an additional condition inside the subquery to order the users three rows by timestamp as well, like this:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score, timeCol
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score
AND mT.timeCol <= m.timeCol) <= 3) tmp
GROUP BY userId
ORDER BY totalScore DESC, MIN(timeCol) ASC;
I am still working on a solution to find out how to handle the scenario where the userid, score, and timestamp are all the same. In that case, you will have to find another tiebreaker. Perhaps you have a primary key column, and you can choose to take a higher/lower primary key?
Query for selecting top three scores from table.
SELECT score FROM result
GROUP BY id
ORDER BY score DESC
LIMIT 3;
Can you please try this?
SELECT score FROM result GROUP BY id ORDER BY score DESC, timestamp ASC LIMIT 3;
if 2 users have same score then it will set order depends on time.
You can use a subquery
SELECT r.userid,
( SELECT sum(r2.score)
FROM results r2
WHERE r2.userid = r.userid
ORDER BY score DESC
LIMIT 3
) as sub
FROM result r
GROUP BY r.userid
ORDER BY sub desc
You should do it like this
SELECT SUM(score) as total, min(timestamp) as first, userid FROM scores
GROUP BY userid
ORDER BY total DESC, first ASC
This is way more efficient than sub queries. If you want to extract more fields than userid, then you need to add them to the group by.
This will of cause not limit the number of scores pr user, which indeed seems to require a subquery to solve.

Progression Series generation so that Show n candidates having highest score and n+5 candidates having second highest score and so on

have a table which have 3 columns. Id is unique and score can be duplicate in that table and table have more than thousand of entries in it.
Id Name Score
Problem statement:-
I have to show list in such manner such that
List of 5:candidate of having highest score then List of 10: candidate of having second highest score then List of 15: candidate of having third highest score
and so on..(Each incremented by 5)
select #mem_count:=#mem_count+5 as group_member_count, scores.score
from (select distinct score from scores_table order by score desc) scores,
(select #mem_count:=0) sess
The query returns list of scores plus member count for each score. That would be subquery to be used in the next statement.
select
sc.*,
#group_n:=if(sc.score!=#group,0,#group_n+1) number_in_group,
#group:=sc.score
from
(SELECT #group_n:=0, #group:=-1) row,
scores_table sc
join (the_subquery) sub on sc.score=sub.score
having number_in_group<sub.group_member_count
Here we define 2 session variables group (to keep current score) and group_n to add numbers in group for each row.
Thus we expect to kind fo group by score and number all rows for each score. Then the having willleave only members less than group_member_count
Use this
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.marks = x.marks
AND y.id <= x.id
GROUP
BY x.marks
, x.id
HAVING COUNT(0) <= (MAX(101-x.marks)*2)
ORDER
BY marks DESC,id;

SQL query to return the three highest values for one column "grouped" by another column

Let's say I have a table like this:
Player Score
A 5
B 4
A 3
B 2
A 1
B 1
A 2
B 3
A 4
B 5
I need an SQL query that will return the three highest scores per player in descending order "grouped" by player i.e.
Player Score
A 5
A 4
A 3
B 5
B 4
B 3
Very grateful for any pointers.
This is old-fashioned (read: basic sql) way of producing top-n per group. You might join the table to itself on group condition (here it is player) and pick records with higher score on right side; if there are three or less such records, the row is one of top n rows per group.
select player.player, player.score
from Player
left join Player p2
on p2.player = player.player
and p2.score > player.score
group by player.player, player.score
having count(distinct p2.score) < 3
order by 1, 2 desc
Alternative version you might check, using not exists:
select player, score
from player
where not exists
(
select p2.player
from Player p2
where p2.player = player.player
and p2.score > player.score
group by p2.player
having count(distinct p2.score) > 3
)
order by 1, 2 desc
This two versions differ in presentation of ties - while first one returns one row (by nature of group by) and needs to be joined back to original table to show all records, second one works directly from original table showing all data and ties at once.
You can find Demo at Sql Fiddle.
in SQL server:
select p.player, p.score
from PS p
where p.score in (select top 3 score from PS
where player = p.player order by score desc)
order by p.player asc, p.score desc
in MySql:
select p.player, p.score
from PS p
where p.score in (select score from PS
where player = p.player order by score desc limit 3)
order by p.player asc, p.score desc
I think what you are looking for can be found here:
http://www.sql-ex.ru/help/select16.php
Basically, the best solution uses the RANK function. Here is the example code from the site:
SELECT maker, model, type FROM
(
SELECT maker, model, type, RANK() OVER(PARTITION BY type ORDER BY model) num
FROM Product
) X
WHERE num <= 3
You would just need to modify the Partition By section to order by your score in descending order.
EDIT
Based upon the information that you will be using MySQL, you will need to make some modifications to the above query (which works with Microsoft SQL). You need to replace the RANK function with your own RANK implementation. It isn't that hard. Complete instructions can be found here:
http://thinkdiff.net/mysql/how-to-get-rank-using-mysql-query/
That will show you how to implement a counter that can give you a rank number.
Depending on what DBMS you use, you may be able to use row_number in some form
In SQL Server 2008 you can use
create table #player
( Player char, Score int )
insert into #player (Player, Score) Values
('A',5),('B',4),('A',3),('B',2),('A',1),('B',1),('A',2),('B',3),('A',4),('B',5)
select * from #player
select Player, Score from
(
select *, ROW_NUMBER() over(partition by Player order by Score desc) as rowNo
from #player
) as tmp
where tmp.rowNo <= 3
drop table #player

Get User Rank Based on Sum with Pagination

There are several rank posts out there but I have yet to see one dealing with when the results are paginated and when the ranking criteria (in this case: points) is equal to the previous user. I have tried a few of the pre-existing examples but none have worked.
I have a table called "users" with the column "id". I also have a table called "points" with the columns "user_id" and "amount".
I need:
1.) Users with duplicate sum of points to have the same rank
Points Table
user_id amount
1 10
2 20
1 5
3 20
3 -5
4 5
Rank should be
rank user_id total
1 2 20
2 1 15
2 3 15
3 4 5
2.) Needs to maintain the ranking from one page to another so the rank has to be gathered in the query and not the resulting PHP.
3.) Display ALL users not just ones with rows in the points table because some users have 0 points and I want to display them last.
Right now I'm just listing the users in order of their points but their rank is not gathered because it wasn't working.
$getfanspoints = mysql_query("SELECT DISTINCT id,
(SELECT SUM(amount) AS points FROM points WHERE points.user_id = users.id) AS points
FROM users
ORDER BY points DESC LIMIT $offset, $fans_limit", $conn);
I've read these solutions and none have worked.
[Roland's Blog][1]
[How to get rank based on SUM's][2]
[MySQL, get users rank][3]
[How to get rank using mysql query][4]
and a few others whose link I can't find right now.
Any suggestions?
[EDIT]
I used ypercube's bottom answer.
SELECT COUNT(*) AS rank
, t.user_id
, t.total
FROM
( SELECT user_id
, SUM(amount) AS total
FROM points
GROUP BY user_id
) AS t
JOIN
( SELECT DISTINCT
SUM(amount) AS total
FROM points
GROUP BY user_id
) AS dt
ON
t.total <= dt.total
GROUP BY t.user_id
ORDER BY rank
, user_id
But the above may be really slow with a big table and points awarded often. It might be really better to have just this and calculate the ranks in your application code:
SELECT users.id AS user_id
, SUM(amount) AS total
FROM
users
LEFT JOIN
points
ON points.user_id = users.id
GROUP BY users.id
ORDER BY total DESC
, user_id
This will work, too (edited, to work with the users table and with OFFSET):
SELECT *
FROM
( SELECT
#rank := #rank + (#t <> total) AS rank
, user_id
, #t := total AS total
FROM
( SELECT users.id AS user_id
, COALESCE(SUM(amount),0) AS total
FROM users
LEFT JOIN points
ON users.id = points.user_id
GROUP BY users.id
) AS o
CROSS JOIN
( SELECT #rank := 0, #t := -999999
) AS dummy
ORDER BY total DESC
, user_id
) tmp
LIMIT x OFFSET y