SQL Group By: Get values of 'max' record - mysql

I have a scores table:
id
user
score
date
Now, I can easily select a top 10 highscore with
SELECT user, score, date FROM scores ORDER BY score DESC
However, I'd like to include only one score per user, namely his highest. I would begin with something like
SELECT user, MAX(score) AS score FROM scores GROUP BY user ORDER BY score DESC
However, now I've lost the date that highest score was recorded. How do I get it?

You can JOIN on the table again:
SELECT s1.user, max(s1.dt), s2.mxscore as score
FROM scores s1
inner join
(
select user, max(score) mxscore
from scores
GROUP BY user
) s2
on s1.user = s2.user
and s1.score = s2.mxscore
GROUP BY s1.username, s2.mxscore
ORDER BY score DESC
See SQL Fiddle with Demo

In fact, you don't need a GROUP BY at all.
Here's the query:
SELECT scores.id, scores.user, scores.score, scores.date
FROM scores
WHERE NOT EXISTS (
SELECT *
FROM scores AS _scores
WHERE _scores.user = scores.user
AND (
_scores.score > scores.score
OR
_scores.score = scores.score AND _scores.id < scores.id)
)
and SQL Fiddle to see it working.
Note that this query properly handles the case when a user had his max score several times (it returns the record for the first max score).

You will need to relate your result with your original table:
select a.user, a.maxScore, b.maxDate
from (
select user, max(score) as maxScore
from scores group by user ) as a
inner join (
select user, score, max(date) as maxDate
from scores group by user, score) as b on a.user = b.user and a.maxScore=b.score
order by
a.maxScore desc
This query will return the maximum score for each user, and the last date when this maximum score was scored (redundant, but true)

SELECT a.*
FROM scores a
JOIN (
SELECT MAX(a.id) AS id
FROM scores a
JOIN (
SELECT user, MAX(score) AS score
FROM scores
GROUP BY user
) b ON a.user = b.user
AND a.score = b.score
GROUP BY a.user,
a.score
) b ON a.id = b.id
ORDER BY a.score DESC
This will account for cases where you have more than one of the same highest score per user. In that case, it will just take the maximum id.

Related

MySQL group by only on specific amount of records (query inside JOIN expression)

I have a History table that represents messages that are stored by some sort of chat program.
It has a user id, message & datetime. (specified on: http://sqlfiddle.com/#!9/49fcefb/5)
Now, what I want is to have a result which contains:
The max amount of messages typed by date
What user has the most messages and howmany messages this user typed)
I got it working, but I find the query rather slow, I think its because of the last GROUP BY (GROUP BY b.cnt) as this will group over all the records found.
Query:
SELECT b.cnt as dayCount, a.cnt as userCount, a.userid as userId, b.date
FROM (
select date_added as date, user_id as userid, count(*) as cnt from history group by userid, day(date_added),month(date_added),year(date_added)
) a
INNER JOIN (
select date_added as date, count(*) as cnt from history group by day(date_added),month(date_added),year(date_added)
) b ON year(a.date) = year(b.date)
AND month(a.date) = month(b.date)
AND day(a.date) = day(b.date)
GROUP BY b.cnt
ORDER BY dayCount desc, userCount desc limit 10;
Can someone advice me on how to solve this? Maybe with another sort of query?
Thanks in advance!
You can try using windows function with Subquery to get the same result.
select SQ.daycount,SQ.usercount,SQ.user_id, SQ.date1 from (
select sum(count(Message)) over (partition by date(date_added)) as daycount, --to get max amount of msg by date
max(count(Message)) over (partition by date(date_added)) as maxuser, -- to identify user with most msg
count(Message) as usercount,
date(date_added) as date1,user_id from history
group by date1,user_id ) SQ
where SQ.usercount=SQ.maxuser
Check the Fiddle here
seems you are joining with wrong criteria.
first table is getting the # of users, 2nd table is getting the #of days per user_id. see dbfiddle
SELECT b.cnt as dayCount, a.cnt as userCount, a.userid as userId, b.date
FROM (
select
user_id as userid
, count(*) as cnt
from history
group by userid
) a
LEFT JOIN (
select cast(date_added as date) as date
, count(*) as cnt
, user_id as userid
from history
group by cast(date_added as date)
) b ON a.userid = b.userid
ORDER BY dayCount desc, userCount desc limit 10;

how can i get four last scores of each course in mysql? [duplicate]

This question already has answers here:
Get top n records for each group of grouped results
(12 answers)
Closed 3 years ago.
my database have four tables: student, course, stu_course, score.
columns for course table: id, name
columns for student table: id, firs_name, last_name, username, password
columns for stu_course table: id, stu_id, course_id
(stu_id and cours_id are foreign keys)
columns fir score table: id, stu_cours_id, score, date
(stu_cours_id is forein ksy)
My question is how can i get four last scores of each course in score table?
here is what i currently have:
SELECT s.first_name
, c.name
, sc.id
, k.score
, k.date
,
FROM student s
JOIN stu_course sc
ON sc.stu_id = s.id
JOIN course c
ON c.id = sc.course_id
JOIN score k
ON k.stu_course_id = sc.id
Thanks for your helps
In MySQL 8.0 (Maria DB >= 10.2), you could use window function ROW_NUMBER() to rank the scores of each course by descending date. Then, just filter out records with a rank higher than 4:
SELECT *
FROM (
SELECT student.first_name, course.name, stu_course.id, score.score, score.date,
ROW_NUMBER() OVER(PARTITION BY course.id ORDER BY score.date DESC) rn
FROM `student`
INNER JOIN stu_course ON stu_course.stu_id = student.id
INNER JOIN course ON course.id = stu_course.course_id
INNER JOIN score ON score.stu_course_id=stu_course.id
) x WHERE rn <= 4
In earlier versions of MySQL/MariaDB, a solution would be to use a correlated subquery:
SELECT
s.first_name,
c.name,
sc.id,
k.score,
k.date
FROM student s
JOIN stu_course sc ON sc.stu_id = s.id
JOIN course c ON c.id = sc.course_id
JOIN score k
ON k.stu_course_id = sc.id
AND (
SELECT COUNT(*) FROM score k1 WHERE k1.stu_course_id = k.stu_course_id AND k1.date > k.date
) <= 3
You can use an ORDER BY statement to sort your list (use order by desc to sort descending) then use LIMIT x to just show the first x results.
Effectively if you use ORDER BY DESC LIMIT x you'll see the bottom x results when sorted by your chosen column

MYSQL sum() returning double score

I'm using the following query to produce a leaderboard, but for some reason it's returning the score as double the value.
SELECT p.user_id, u.first, SUM(points) AS score FROM points AS p LEFT
JOIN users AS u ON p.user_id = u.fb_id WHERE p.action_time >
'1492732800' GROUP BY p.user_id ORDER BY points DESC LIMIT 10
What might be causing this to happen?
Thanks
One workaround is to aggregate the points in a separate subquery and join to that:
SELECT t1.fb_id, t1.first, t2.score
FROM users t1
INNER JOIN
(
SELECT user_id, SUM(points) AS score
FROM points
WHERE action_time > '1492732800'
GROUP BY user_id
) t2
ON t2.user_id = t1.fb_id

Query on two tables with belongs_to/has_many relation

One table is Users with id and email columns.
Another table is Payments with id, created_at, user_id and foo columns.
User has many Payments.
I need a query that returns each user's email, his last payment date and this last payment's foo value. How do I do that? What I have now is:
SELECT users.email, MAX(payments.created_at), payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
GROUP BY users.id
This is wrong, because foo value does not necessarily belong to user's most recent payment.
Try this :
select users.email,foo,create_at
from users
left join(
select a.* from payments a
inner join (
select id,user_id,max(create_at)
from payments
group by id,user_id
)b on a.id = b.id
) payments on users.id = payments.user_id
If users has no payment yet, then foo and create_at would return NULL. if you want to exclude users who has no payment, then use INNER JOIN.
One approach would be to use a MySQL version of rank over partition and then select only those rows with rank = 1:
select tt.email,tt.created_at,tt.foo from (
select t.*,
case when #cur_id = t.id then #r:=#r+1 else #r:=1 end as rank,
#cur_id := t.id
from (
SELECT users.id,users.email, payments.created_at, payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
order by users.id asc,payments.created_at desc
) t
JOIN (select #cur_id:=-1,#r:=0) r
) tt
where tt.rank =1;
This would save hitting the payments table twice. Could be slower though. Depends on your data!

MySQL - selecting rows around a certain row criteria

I'm trying to select just the neighboring N and M rows before and after a certain row criteria, for a high scores table that focuses on the user's personal score (compared with players with similar scores N above and M below).
scores
-------
id:int
username:varchar(120)
score:int
Note: each username has multiple scores. The high scores database is simply a score dump
So, to fetch just the top 10 global scores: SELECT max(score),username FROM scores GROUP BY username, ORDER BY score DESC
However, I'm trying to do this for any arbitrary user - most of which aren't lucky enough to fall in the top 10...
How do I reference the N rows above and M rows below a certain user's score, in order to pull 10 scores above and below a user's score?
To get the N results above the user, assuming all scores are distinct:
select s.*
from scores
where s.score > (select s.score from scores where username = $username)
order by score desc
limit N;
To get the M scores less than a given users score:
select s.*
from scores
where s.score < (select s.score from scores where username = $username)
order by score asc
limit M;
Having identical scores poses a little challenge. The following combines the above two with a union all and fixes this problem:
(select s.*
from scores s cross join
(select * from scores where username = $username) u
where s.score > u.score or
s.score = u.score and s.id > u.id
order by s.score desc, s.id desc
limit N
) union all
(select s.*
from scores s cross join
(select * from scores where username = $username) u
where s.score < u.score or
s.score = u.score and s.id < u.id
order by s.score, s.id
limit M
)