Negative limit offset in mysql - mysql

I'm creating a high score server and one of the needed features is being able to retrieve high scores around the users current score. I currently have the following:
SELECT * FROM highscores
WHERE score >= ( SELECT score FROM highscores WHERE userID = someID )
ORDER BY score, updated ASC
LIMIT -9, 19
The only problem here is that the offset parameter of LIMIT can't be negative, otherwise I believe this would work dandy. So in conclusion, is there any trick / way to supply a negative offset to the LIMIT offset, or is there perhaps a better way to about this entirely?

You can either do a real pain in the butt single select query, or just do this:
(SELECT * FROM highscores
WHERE score <= ( SELECT score FROM highscores WHERE userID = someID )
ORDER BY score, updated ASC
LIMIT 9)
UNION
(SELECT * FROM highscores
WHERE score = ( SELECT score FROM highscores WHERE userID = someID ))
UNION
(SELECT * FROM highscores
WHERE score >= ( SELECT score FROM highscores WHERE userID = someID )
ORDER BY score, updated ASC
LIMIT 9)
I threw in a piece to grab the indicated user's score so it's in the middle of the list. Optional if you need it. Also, don't use SELECT *, use specific fields. Clarity is always preferable, and performance wise, * sucks.

Related

Add second column to order by mysql in a group_concat

I have this query I made based on someone else question here.
SELECT *, FIND_IN_SET( score, ( SELECT GROUP_CONCAT( score ORDER BY score ASC) FROM EventPlayerResult WHERE eventId = 'EventTest0') ) AS position FROM EventPlayerResult WHERE eventId = 'EventTest0' ORDER BY position ASC LIMIT 10
It gives me a leaderboard for the top 10 players. But when I run it, if 2 players has the same score, I need it to filter by another column (energyLeft). So I tried to add , energyLeft DESC inside of my GROUP_CCONCAT but it doesnt change anything. im not familiar with group concat and find in set. So where should I add the logic to order by energyLeft after ordering by score.
I tried something like this :
SELECT *, FIND_IN_SET( score, ( SELECT GROUP_CONCAT( score ORDER BY score ASC, energyLeft DESC) FROM EventPlayerResult WHERE eventId = 'EventTest0') ) AS position FROM EventPlayerResult WHERE eventId = 'EventTest0' ORDER BY position ASC LIMIT 10
You should use the player and not the score inside GROUP_CONCAT() so that the players are ranked by score first and then by energyLeft.
Assuming there is a column like player_id in the table:
SELECT *,
FIND_IN_SET(
player_id,
(
SELECT GROUP_CONCAT(player_id ORDER BY score ASC, energyLeft DESC) FROM EventPlayerResult WHERE eventId = 'EventTest0'
)
) AS position
FROM EventPlayerResult
WHERE eventId = 'EventTest0'
ORDER BY position ASC LIMIT 10;

SQL query with a major NOT IN not working

Does anyone know what's wrong with this query?
This works perfectly on its own:
SELECT * FROM
(SELECT * FROM data WHERE site = '".$id."'
AND disabled = '0'
AND carvotes NOT LIKE '0'
AND (time > ( now( ) - INTERVAL 14 DAY ))
GROUP BY car ORDER BY carvotes DESC LIMIT 0 , 10)
X order by time DESC
So does this:
SELECT * FROM data WHERE site = '".$id."' AND disabled = '0' GROUP BY car DESC ORDER BY time desc LIMIT 0 , 30
But combining them like this:
SELECT * FROM data WHERE site = '".$id."' AND disabled = '0' AND car NOT IN (SELECT * FROM
(SELECT * FROM data WHERE site = '".$id."'
AND disabled = '0'
AND carvotes NOT LIKE '0'
AND (time > ( now( ) - INTERVAL 14 DAY ))
GROUP BY car ORDER BY carvotes DESC LIMIT 0 , 10)
X order by time DESC) GROUP BY car DESC ORDER BY time desc LIMIT 0 , 30
Gives errors. Any ideas?
Please try the following...
$result = mysqli_query( $con,
"SELECT *
FROM data
WHERE site = '" . $id .
"' AND disabled = '0'
AND car NOT IN ( SELECT car
FROM ( SELECT car,
carvotes
FROM data
WHERE site = '" . $id .
"' AND disabled = '0'
AND carvotes NOT LIKE '0'
AND ( time > ( NOW( ) - INTERVAL 14 DAY ) )
GROUP BY car
ORDER BY carvotes DESC
LIMIT 10 ) X
)
GROUP BY car
ORDER BY time DESC
LIMIT 30" );
The main cause of your problem is that with car NOT IN ( SELECT * FROM ( SELECT *... you are trying to compare each record's value of car with each row returned by your subquery. IN requires you to have the same number of fields on both sides of the comparison. By using SELECT * at both levels of the subquery you were ensuring that the right side of the comparison had however many fields are in data versus your single field on the left, which confused MySQL.
Since you are aiming to compare to a single field, namely car, our subquery has to select just the car field from its dataset. Since the sort order of the subquery's results has no effect upon the IN comparison, and since our innermost query will be returning just car, I have removed the outer level of the subquery.
Beyond changing the first part of the subquery to SELECT car, the only other change that I have made to the subquery is to change LIMIT 0, 10 to LIMIT 10. The former means limit to the the 10 records that are offset by 0 from the first record. This is useful if you want records 6 to 15, but redundant for 1 to 10 as LIMIT 10 has the same affect and is slightly simpler. Ditto for LIMIT 0, 30 at the end of your overall statement.
As for the main body of the statement, I have not made any attempt to specify what fields (or aggregate functions of those fields) should be returned since you have made no statement indicating what your requirements / preferences are. If you are satisfied that GROUP BY has left you with a still valid set of values, then all the good, but if not then I recommend that you rewrite your Question to be specific about that detail.
By default, MySQL sorts the data subjected to a GROUP BY into ascending order, but if an ORDER BY clause is also present then it overrides the GROUP BY's sort pattern. As such, there is no benefit to specifying DESC after either of your GROUP BY car clauses, so I have removed it where it occurs.
Interesting Sidenote : You can override a GROUP BY's sort by specifying ORDER BY NULL.
If you have any questions or comments, then please feel free to post a Comment accordingly.
Further Reading
https://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html - on optimising your ORDER BY sorting
https://dev.mysql.com/doc/refman/5.7/en/select.html - on the SELECT statement's syntax - specifically the parts to do with LIMIT.
https://www.w3schools.com/php/php_mysql_select_limit.asp - a simpler explanation of LIMIT
This is your query:
SELECT *
FROM data
WHERE site = '".$id."' AND disabled = '0' AND
car NOT IN (SELECT *
FROM (SELECT *
FROM data
WHERE site = '".$id."' AND
disabled = '0' AND
carvotes NOT LIKE '0' AND
(time > ( now( ) - INTERVAL 14 DAY ))
GROUP BY car
ORDER BY carvotes DESC
LIMIT 0 , 10
) x
ORDER BY time DESC
)
GROUP BY car DESC
ORDER BY time desc
LIMIT 0 , 30 ;
Several comments:
Do not wrap integer constants in single quotes. This can mislead people. This can mislead optimizers.
Do not use string functions on integers (such as like). Same reason.
NOT IN with subqueries is dangerous. The construct does not handle NULL values the way you expect. Use NOT EXISTS or LEFT JOIN instead.
When using subqueries, ORDER BY is almost never appropriate.
Never use SELECT * with GROUP BY. It is just wrong. Happily, MySQL 5.7 has changed its defaults to reject this anti-pattern
So, a better way to write this query is something like this:
SELECT d.car, MAX(time) as time
FROM data d LEFT JOIN
(SELECT d2.*
FROM data d2
WHERE d2.site = '".$id."' AND
d2.disabled = 0 AND
d2.carvotes NOT LIKE 0 AND
(d2.time > ( now( ) - INTERVAL 14 DAY ))
GROUP BY d2.car
ORDER BY carvotes DESC
LIMIT 0 , 10
) car10
ON d.car = car10.car
WHERE d.site = '".$id."' AND d.disabled = 0' AND
car10.car IS NOT NULL
GROUP BY car DESC
ORDER BY MAX(time) desc
LIMIT 0 , 30 ;
Alternatively, use SELECT * and remove the GROUP BY in the outer query.

MySQL FROM Subquery with parent group by

I need to show a ranking lists for a sport we manage.
It needs to sum up the 4 best results for each player (from a table that could have hundreds of results per player) and then sort the entire list from the player with the most points to least points.
The query below returns
ERROR 1054 (42S22): Unknown column 'r1.user_id' in 'where clause'
so I've gone off track somewhere.
SELECT r1.user_id, (
SELECT SUM(points)
FROM (
SELECT *
FROM ranking_matrix_points r2
WHERE user_id=r1.user_id
ORDER BY points DESC
LIMIT 4
) r3
) AS total_points
FROM ranking_matrix_points r1
WHERE
user.status IN('active')
GROUP BY r1.user_id
ORDER BY total_points DESC
One possible solution might be to number the rows for each user in order of points descending and then sum up the points with a rank <= 4. This might not perform very well though, and also you'll get a problem with ties (but you would have using limit too).
select user_id, sum(points) total_points
from (
select user_id, points,
(
case user_id
when #cur_user
then #row := #row + 1
else #row := 1 and #cur_user := user_id end
) as rank
from ranking_matrix_points,
(select #row := 0, #cur_user := '') r
order by user_id, points desc
) t
where rank <= 4
group by user_id;
I'm pretty sure there are better ways to do this but I can't think of any at the moment. This would have been very easy in just about any database with support for window functions, but sadly MySQL doesn't support any yet.
You don't need a double query, just
SELECT user_id, SUM(points)
FROM ranking_matrix_points
WHERE user.status in('active')
GROUP BY user_id
ORDER BY total_points DESC
LIMIT 4
or
SELECT TOP 4 user_id, SUM(points)
FROM ranking_matrix_points
WHERE user.status in('active')
GROUP BY user_id
ORDER BY total_points DESC

MySQL: avg per id in single column

For a graph I am asked to structure the data with an MYSQL statement.
It needs the following output (3 colums):
AVG_points | Playerid | Date
In the database I have dont have the average points per roundID, just the points scored per round per player.
its easy to calculate the current average points with avg(points) but I need the average points on every round so it can be plotted out in a graph.
I tried to make an SQL statement to give me the averages for every round but its not comming out in an useable format for graph to plot. I read into Pivotting but thats not what works in this situation, I think my sql is to simple, plus for every new round that comes up i need to program more lines wich means manual edits every table update to get the graph to work...
this what I tried:
SELECT t1.playerid as player
,date_format(t1.CreatedTime, '%Y%m%d%H%i') as date
/* calculate average points per round */
,(select avg(points) from pokermax_scores t2 where tournamentid <= (select distinct(tournamentid) from pokermax_scores order by tournamentid desc limit 0,1) and t2.playerid = t1.playerid) as avg_current
,(select avg(points) from pokermax_scores t2 where tournamentid <= (select distinct(tournamentid) from pokermax_scores order by tournamentid desc limit 1,1) and t2.playerid = t1.playerid) as 1_avg_last
,(select avg(points) from pokermax_scores t2 where tournamentid <= (select distinct(tournamentid) from pokermax_scores order by tournamentid desc limit 2,1) and t2.playerid = t1.playerid) as 2_avg_last
,(select avg(points) from pokermax_scores t2 where tournamentid <= (select distinct(tournamentid) from pokermax_scores order by tournamentid desc limit 3,1) and t2.playerid = t1.playerid) as 3_avg_last
,(select avg(points) from pokermax_scores t2 where tournamentid <= (select distinct(tournamentid) from pokermax_scores order by tournamentid desc limit 4,1) and t2.playerid = t1.playerid) as 4_avg_last
,(select avg(points) from pokermax_scores t2 where tournamentid <= (select distinct(tournamentid) from pokermax_scores order by tournamentid desc limit 5,1) and t2.playerid = t1.playerid) as 5_avg_last
FROM pokermax_scores as t1, pokermax_players as t3
GROUP BY player
which gives the following output: < SEE SQLFIDDLE LINK >
but I need my data in this format so PHP can loop it correctly:
http://i57.tinypic.com/10wists.png
Is there any SQL guru here that knows how I can edit my statement to make it come out as above picture?
thanks for reading all this :)
Here the SQLFIDDLE: http://sqlfiddle.com/#!9/a956f/2
It is unclear what your data really looks like. The following would seem to be a good place to start because it produces the output in the format you want:
SELECT date(ps.CreatedTime) as date,
ps.playerid as player,
avg(ps.score)
FROM pokermax_scores ps
GROUP BY date(ps.CreatedTime), ps.playerid;
EDIT:
The comment helps. You have nothing called "round" in the data.
I'm guessing it is tournamentid. The query is easily modified if it is something else. I think you want two levels of aggregation:
SELECT date, player, avg(score)
FROM (SELECT date(ps.CreatedTime) as date,
ps.playerid as player, tournamentid,
SUM(ps.points) as score
FROM pokermax_scores ps
GROUP BY date(ps.CreatedTime), ps.playerid, tournamentid
) dpt
GROUP BY date, player;
Here it is in a SQL Fiddle.
You could use this to get cascaded sums on tournament points:
set #score_accumulator=0;
set #accumulator=0;
SELECT sub.tournamentId,
sub.datePS,
-- SUM(#score_accumulator := #score_accumulator + sub.points) score,
-- #accumulator := #accumulator + 1 nrTour,
SUM(#score_accumulator := #score_accumulator + sub.points)/(#accumulator := #accumulator + 1) score
FROM
(SELECT DISTINCT tournamentId,
date(CreatedTime) AS datePS,
points points
FROM pokermax_scores ps2
ORDER BY CreatedTime) sub
GROUP BY sub.tournamentId ORDER BY sub.datePS
Similarly, you could to this for players points evolution.
That being said, this kind of logic should reside at application level and not DB level.

MySQL position in recordset

I'm ordering a recordset like this:
SELECT * FROM leaderboards ORDER BY time ASC, percent DESC
Say I have the id of the record which relates to you, how can I find out what position it is in the recordset, as ordered above?
I understand if it was just ordered by say 'time' I could
SELECT count from table where time < your_id
But having 2 ORDER BYs has confused me.
You can use a variable to assign a counter:
SELECT *, #ctr := #ctr + 1 AS RowNumber
FROM leaderboards, (SELECT #ctr := 0) c
ORDER BY time ASC, percent DESC
Does this do what you want?
SELECT count(*)
FROM leaderboards lb cross join
(select * from leaderboards where id = MYID) theone
WHERE lb.time < theone.time or
(lb.time = theone.time and lb.percent >= theone.percent);
This assumes that there are no duplicates for time, percent.