MySQL - selecting rows around a certain row criteria - mysql

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
)

Related

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

Display only top sum-up results using group by

My table records three different scores (site, park, division) for the same group of user.
A user's total score is "sum of all the site scores" + "sum of all the park scores" + "sum of all the division scores".
The code is the following and I am using MySQL:
select sum(points)as points, user_name
from
(SELECT sum(site_point) as points, user_name from visits v join users u on
u.user_id = v.user_id group by user_name
union
select sum(park_point) as points , user_name from visits v join users u on
u.user_id = v.user_id group by user_name
union
select sum(division_point) as points , user_name from visits v join users u
on u.user_id = v.user_id group by user_name
) V group by user_name order by sum(points) DESC ;
I want to display only the users whose score is in top 5 and their scores.
Ten users may have the same highest score. I need all of them being displayed.
I appreciate any help.
I think this is what you want:
select user_name,
sum(site_point + park_point + division_point) as points
from visits v join
users u
on u.user_id = v.user_id
group by user_name
order by points desc
limit 5;
A single aggregation simplifies the calculation.
You may not realize it, but the union is going to return incorrect results under some circumstances. Union removes duplicates, so if a user has the same partial scores, then the two rows becomes one.
EDIT:
It is a little trickier to get the top 5 scores, but possible:
select user_name,
sum(site_point + park_point + division_point) as points
from visits v join
users u
on u.user_id = v.user_id
group by user_name
having points >= (select distinct points
from (select sum(site_point + park_point + division_point)
from visits v join
users u
on u.user_id = v.user_id
group by user_name
) vu
order by points desc
limit 1 offset 4
)
order by points desc
limit 5;
Assuming that a user_name is unique for a given id you can simplify this a wee bit:
having points >= (select distinct points
from (select sum(site_point + park_point + division_point) as points
from visits v
group by user_id
) vu
order by points desc
limit 1 offset 4
)
And, if you want anyone who matches the top 5 users -- but to return more than 5 rows if there are ties -- then change the select distinct to select in the subquery.
Use the LIMIT command. More info here
So to tack it onto your SQL would result in:
select sum(points)as points, user_name
from
(SELECT sum(site_point) as points, user_name from visits v join users u on
u.user_id = v.user_id group by user_name
union
select sum(park_point) as points , user_name from visits v join users u on
u.user_id = v.user_id group by user_name
union
select sum(division_point) as points , user_name from visits v join users u
on u.user_id = v.user_id group by user_name
) V group by user_name order by sum(points) DESC LIMIT 5

MySQL - query for sorting

Example data:
username score time
-------- ----- ----
jim 90 50
jim 90 30
paul 50 30
bob 100 90
tim 90 20
I want to be able to sort by score in descending order. If two scores are the same, sort by the lowest time. If there are multiple rows with the same username, I only want one. So the above table should look like this:
username score time
-------- ----- ----
bob 100 90
tim 90 20
jim 90 30
paul 50 30
I have a query that nearly produces this, however, if the scores are the same, it doesn't sort by lowest time. How would I do this?
SELECT U.username, S.score, S.time
FROM scores S JOIN users U ON S.user_id=U.user_id
WHERE NOT EXISTS (
SELECT 1 FROM scores R2 WHERE R2.user_id = S.user_id and R2.score_id > S.score_id
)
AND test_id=0
AND module_id=1
ORDER BY score DESC, time ASC;
Avoiding using a sub query and using a LEFT OUTER JOIN instead.:-
SELECT U.username, s1.score, s1.time
FROM scores s1
INNER JOIN users U
ON s1.user_id = U.user_id
LEFT OUTER JOIN scores s2
ON s1.user_id = s2.user_id
AND (s1.score < s2.score
OR (s1.score = s2.score
AND s1.time > s2.time))
WHERE s2.user_id IS NULL
ORDER BY s1.score DESC, s1.time ASC
This does a LEFT OUTER JOIN against the score table looking for a match for the same user with a higher score, or the same score and a faster time. Any where these are found are not the best score so are dropped with the WHERE clause
SQL fiddle for this:-
http://sqlfiddle.com/#!2/7f3e93/3
Edit - adding the test_id and module_id checks if required:-
SELECT U.username, s1.score, s1.time
FROM scores s1
INNER JOIN users U
ON s1.user_id = U.user_id
LEFT OUTER JOIN scores s2
ON s1.user_id = s2.user_id
AND s2.test_id = 0
AND s2.module_id = 1
AND (s1.score < s2.score
OR (s1.score = s2.score
AND s1.time > s2.time))
WHERE s2.user_id IS NULL
AND s1.test_id = 0
AND s1.module_id = 1
ORDER BY s1.score DESC, s1.time ASC
SELECT U.username, max(S.score) as maxscore, min(S.time) as mintime
FROM scores S
JOIN users U ON S.user_id=U.user_id
WHERE NOT EXISTS
(
SELECT 1 FROM scores R2
WHERE R2.user_id = S.user_id and R2.score_id > S.score_id
)
AND test_id=0
AND module_id=1
GROUP BY U.username
ORDER BY maxscore DESC, mintime ASC;

SQL Group By: Get values of 'max' record

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.

Error with a join query

SELECT r.game, u.username, r.userid, r.points, u.format
FROM ".TBL_RANKING." r
INNER JOIN ".TBL_USERS." u
ON u.id = r.userid
WHERE r.type = '1' AND r.game =
(SELECT name
FROM ".TBL_GAME."
WHERE active = '1'
ORDER BY rand()
LIMIT 1)
AND u.format =
(SELECT name
FROM ".TBL_FORMAT."
WHERE active = '1'
ORDER BY rand() LIMIT 1)
ORDER BY r.points DESC LIMIT 5
This query isn't working as it's supposed to. It's selecting the odd user and sometimes none at all.
The query should:
-select a random game from the game table
-select a random format from the format table
-select users ranked on that game but only from the format selected
so if the random selection was FIFA 12 Xbox 360, it would find all users that are from format type Xbox 360 and are ranked on FIFA 12.
The table structure is as follows:
*tbl_ranking*
id
userid
game
points
type
*tbl_users*
id
username
format
*tbl_game*
id
name
*tbl_format*
id
name
Can anyone see a problem here?
try to have sub queries using left join
SELECT r.game, u.username, r.userid, r.points, u.format
FROM TBL_RANKING r
INNER JOIN TBL_USERS u
ON u.id = r.userid
LEFT JOIN (SELECT name FROM ".TBL_GAME." WHERE active = '1' ORDER BY rand() LIMIT 1) temp1
ON r.game=temp1.name
LEFT JOIN (SELECT name FROM ".TBL_FORMAT." WHERE active = '1' ORDER BY rand() LIMIT 1) temp2
ON u.format=temp2.name
WHERE r.type = '1'
AND temp1.name != ''
AND temp2.name != ''
ORDER BY r.points DESC LIMIT 5