MySQL backward lookup with forward result - mysql

I have a scenario where I need to obtain a set of results in ascending order but only given the last ID in the descending sequence.
The order will always be sequentially ascending as the ID is auto generated however, I cannot guarantee that ID sequence is unbroken. i.e. I cannot say 8 - 3 = 5 therefore do a forward lookup from 5.
What is the most efficient was of achieving this. If possibly I want to avoid manipulating the collection in the application logic but can as a last resort.

You could do with an inner select:
SELECT *
FROM (
SELECT *
FROM your_table
WHERE id <= X
ORDER BY id DESC
LIMIT Y
) d
ORDER BY id ASC

You can define your own rownum with query and apply mod to get every 3rd record regardless of it's id, e.g.:
SELECT *
FROM (SELECT id, name, #rownum := #rownum + 1 as `num`
FROM test, (SELECT #rownum := 0) a ) b
WHERE b.num % 3 = 0;
Here's the SQL Fiddle.

Related

using FORCE INDEX to ensure the table is ordered with GROUP BY and ORDER BY before calculating user variables

I am trying to sum the nth highest rows.
I am calculating a cycling league table where 1st fastest rider at an event gets 50 points 2nd fastest 49 points and so on .... there are 10 events over the league but only a rider's 8 best results are used (this means a rider can miss up to 2 events without a catastrophic decent down the leader board)
first i need a table where each rider's results from all events in the league are grouped together and listed in order of highest points, and then a sequential number calculated so i can sum the 8 or less best results.
so i used this table select:
set #r := 0, #rn := 0 ;
SELECT
t.*,
#rn := if(#r = t.id_rider, #rn + 1, 1) as seqnum,
#r := t.id_rider as dummy_rider
from results as t
ORDER BY t.id_rider, t.points desc
where the table results is a view as below:
SELECT
a.id_rider,
b.id_event,
b.race_no,
b.id_race,
b.id_race_type,
b.`position`,
c.id_league,
(51 - b.`position`) AS points
FROM
wp_dtk_start_sheet a
JOIN wp_dtk_position_results b ON a.id_event = b.id_event AND a.race_no = b.race_no
JOIN wp_dtk_league_races c ON b.id_race = c.id_race
WHERE
c.id_league = 1
AND b.`position` IS NOT NULL
this does not work as the seqnum is 1 for all results. if i export the view table into excel and crate a test table with the same columns and data it works ok. i believe what is going wrong is that the table is not being sorted by ORDER BY t.id_rider, t.points desc before running through the variables
this reference: https://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/ states " This technique is pretty much non-deterministic, because it relies on things that you and I don’t get to control directly, such as which indexes MySQL decides to use for grouping"
this reference suggest trying to force the index to use id_rider so i tried:
set #r := 0, #rn := 0 ;
SELECT
a.id_rider,
c.id_league,
(51- b.`position`) as points,
#rn := if(#r = a.id_rider, #rn + 1, 1) as seqnum,
#r := a.id_rider as 'set r'
from wp_dtk_start_sheet as a force index (id_rider)
join wp_dtk_position_results as b on a.id_event = b.id_event and a.race_no = b.race_no
join wp_dtk_league_races as c on b.id_race = c.id_race
where c.id_league = 1 and b.`position` is not null
ORDER BY a.id_rider, points desc
this did not work i got seqnum =1 for all rows as before
my table structure is as below:
table a - wp_dtk_start_sheet
table b - wp_dtk_position_results
table c -wp_dtk_league_races
this stack overlow answer was also very helpfull but also has the same problem with it:
Sum Top 10 Values
can anyone help? perhaps i am going about this all the wrong way?
The solution is much more clear if you use window functions. This allows you to specify the order of rows within each group for purposes of row-numbering.
SELECT t.*
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY id_rider ORDER BY points DESC) AS seqnum
FROM results
) AS t
WHERE t.seqnum <= 8;
Support for window functions in MySQL was introduced in version 8.0, so you might have to upgrade. But it's been part of the MySQL product since 2018.
Bill's answer works brilliantly but I have also combined it into one statement as well, this is the combined select command:
Select
t.id_rider,
sum(points) as total
from
(SELECT
a.id_rider,
c.id_league,
(51- b.`position`) as points,
ROW_NUMBER() OVER (PARTITION BY id_rider ORDER BY points DESC) AS seqnum
from wp_dtk_start_sheet as a
join wp_dtk_position_results as b on a.id_event = b.id_event and a.race_no = b.race_no
join wp_dtk_league_races as c on b.id_race = c.id_race
where c.id_league = 1 and b.`position` is not null ) as t
where seqnum <= 8
group by id_rider
order by total desc

Is it possible to limit the rows per value in a WHERE col IN(...) statement?

I have a query like this:
SELECT * FROM table WHERE user_id IN (val_1, val_2, ..., val_n) ORDER BY timestamp DESC LIMIT 15;
As I have explored in an earlier post there is no way to make this use an index for the ordering.
But is there any way to limit the amount of rows MySql stores in a temporary table for each value in the IN(...) statement?
In other words, tell the db to fetch x rows where user_id = value_1, then x rows from where user_id = value_2, and so on.
I need to limit this as I don't want the db to do a sort on too many rows and I just need the most recent entries of each user.
I have already solved this by adding AND WHERE count <= m and I make sure that the most recent m entries of the user have a count <= m but this is slowing down my system when entries are made as I have to increase count on m-1 entries and set count to > m on the oldest one.
Using FULL JOIN is not an option. It does what I want but the execution of all the joins is very slow (I would need 50+ joins).
One method is brute force:
SELECT *
FROM table
WHERE user_id = val_1
ORDER BY timestamp DESC
LIMIT 15
UNION ALL
SELECT *
FROM table
WHERE user_id = val_2
ORDER BY timestamp DESC
LIMIT 15
UNION ALL
. . .
Although this may seem "brute force", it can take advantage of an index on (user_id, timestamp), so it might be efficient.
If you want up to "n" rows for each value, then variables can help:
SELECT t.*
FROM (SELECT t.*,
(#rn := if(#u = user_id, #rn + 1,
if(#u := user_id, 1, 1)
)
) as rn
FROM table t CROSS JOIN
(SELECT #rn := 0, #u := -1) params
ORDER BY user_id, timestamp DESC
) t
WHERE rn <= 15
ORDER BY user_id, timestamp DESC;
If you need the most recent entry per user then you are looking at a GROUPWISE MAXIMUM problem. See the solutions in the MySQL manual tutorial

Select first and last match by column from a timestamp-ordered table in MySQL

Stackoverflow,
I need your help!
Say I have a table in MySQL that looks something like this:
-------------------------------------------------
OWNER_ID | ENTRY_ID | VEHICLE | TIME | LOCATION
-------------------------------------------------
1|1|123456|2016-01-01 00:00:00|A
1|2|123456|2016-01-01 00:01:00|B
1|3|123456|2016-01-01 00:02:00|C
1|4|123456|2016-01-01 00:03:00|C
1|5|123456|2016-01-01 00:04:00|B
1|6|123456|2016-01-01 00:05:00|A
1|7|123456|2016-01-01 00:06:00|A
...
1|999|123456|2016-01-01 09:10:00|A
1|1000|123456|2016-01-01 09:11:00|A
1|1001|123456|2016-01-01 09:12:00|B
1|1002|123456|2016-01-01 09:13:00|C
1|1003|123456|2016-01-01 09:14:00|C
1|1004|123456|2016-01-01 09:15:00|B
...
Please note that the table schema is just made up so I can explain
what I'm trying to accomplish...
Imagine that from ENTRY_ID 6 through 999, the LOCATION column is "A". All I need for my application is basically rows 1-6, then row 1000 onwards. Everything from row 7 to 999 is unnecessary data that doesn't need to be processed further. What I am struggling to do is either disregard those lines without having to move the processing of the data into my application, or better yet, delete them.
I'm scratching my head with this because:
1) I can't sort by LOCATION then just take the first and last entries, because the time order is important to my application and this will become lost - for example, if I processed this data in this way, I would end up with row 1 and row 1000, losing row 6.
2) I'd prefer to not move the processing of this data to my application, this data is superfluous to my requirements and there is simply no point keeping it if I can avoid it.
Given the above example data, what I want to end up with once I have a solution would be:
-------------------------------------------------
OWNER_ID | ENTRY_ID | VEHICLE | TIME | LOCATION
-------------------------------------------------
1|1|123456|2016-01-01 00:00:00|A
1|2|123456|2016-01-01 00:01:00|B
1|3|123456|2016-01-01 00:02:00|C
1|4|123456|2016-01-01 00:03:00|C
1|5|123456|2016-01-01 00:04:00|B
1|6|123456|2016-01-01 00:05:00|A
1|1000|123456|2016-01-01 09:11:00|A
1|1001|123456|2016-01-01 09:12:00|B
1|1002|123456|2016-01-01 09:13:00|C
1|1003|123456|2016-01-01 09:14:00|C
1|1004|123456|2016-01-01 09:15:00|B
...
Hopefully I'm making sense here and not missing something obvious!
#Aliester - Is there a way to determine that a row doesn't need to be
processed from the data contained within that row?
Unfortunately not.
#O. Jones - It sounds like you're hoping to determine the earliest and
latest timestamp in your table for each distinct value of ENTRY_ID,
and then retrieve the detail rows from the table matching those
timestamps. Is that correct? Are your ENTRY_ID values unique? Are they
guaranteed to be in ascending time order? Your query can be made
cheaper if that is true. Please, if you have time, edit your question
to clarify these points.
I'm trying to find the arrival time at a location, followed by the departure time from that location. Yes, ENTRY_ID is a unique field, but you cannot take it as a given that an earlier ENTRY_ID will equal an earlier timestamp - the incoming data is sent from a GPS unit on a vehicle and is NOT necessarily processed in the order they are sent due to network limitations.
This is a tricky problem to solve in SQL because SQL is about sets of data, not sequences of data. It's extra tricky in MySQL because other SQL variants have a synthetic ROWNUM function and MySQL doesn't as of late 2016.
You need the union of two sets of data here.
the set of rows of your database immediately before, in time, a change in location.
the set of rows immediately after a change in location.
To get that, you need to start with a subquery that generates all your rows, ordered by VEHICLE then TIME, with row numbers. (http://sqlfiddle.com/#!9/6c3bc7/2/0) Please notice that the sample data in Sql Fiddle is different from your sample data.
SELECT (#rowa := #rowa + 1) rownum,
loc.*
FROM loc
JOIN (SELECT #rowa := 0) init
ORDER BY VEHICLE, TIME
Then you need to self-join that subquery, use the ON clause to exclude consecutive rows at the same location, and take the rows right before a change in location. Comparing consecutive rows is done by ON ... b.rownum = a.rownum+1. That is this query. (http://sqlfiddle.com/#!9/6c3bc7/1/0)
SELECT a.*
FROM (
SELECT (#rowa := #rowa + 1) rownum,
loc.*
FROM loc
JOIN (SELECT #rowa := 0) init
ORDER BY VEHICLE, TIME
) a
JOIN (
SELECT (#rowb := #rowb + 1) rownum,
loc.*
FROM loc
JOIN (SELECT #rowb := 0) init
ORDER BY VEHICLE, TIME
) b ON a.VEHICLE = b.VEHICLE
AND b.rownum = a.rownum + 1
AND a.location <> b.location
A variant of this subquery, where you say SELECT b.*, gets the rows right after a change in location (http://sqlfiddle.com/#!9/6c3bc7/3/0)
Finally, you take the setwise UNION of those two queries, order it appropriately, and you have your set of rows with the duplicate consecutive positions removed. Please notice that this gets quite verbose in MySQL because the nasty #rowa := #rowa + 1 hack used to generate row numbers has to use a different variable (#rowa, #rowb, etc) in each copy of the subquery. (http://sqlfiddle.com/#!9/6c3bc7/4/0)
SELECT a.*
FROM (
SELECT (#rowa := #rowa + 1) rownum,
loc.*
FROM loc
JOIN (SELECT #rowa := 0) init
ORDER BY VEHICLE, TIME
) a
JOIN (
SELECT (#rowb := #rowb + 1) rownum,
loc.*
FROM loc
JOIN (SELECT #rowb := 0) init
ORDER BY VEHICLE, TIME
) b ON a.VEHICLE = b.VEHICLE AND b.rownum = a.rownum + 1 AND a.location <> b.location
UNION
SELECT d.*
FROM (
SELECT (#rowc := #rowc + 1) rownum,
loc.*
FROM loc
JOIN (SELECT #rowc := 0) init
ORDER BY VEHICLE, TIME
) c
JOIN (
SELECT (#rowd := #rowd + 1) rownum,
loc.*
FROM loc
JOIN (SELECT #rowd := 0) init
ORDER BY VEHICLE, TIME
) d ON c.VEHICLE = d.VEHICLE AND c.rownum = d.rownum - 1 AND c.location <> d.location
order by VEHICLE, TIME
And, in next-generation MySQL, available in beta now in MariaDB 10.2, this is much much easier. The new generation as common table expressions and row numbering.
with loc as
(
SELECT ROW_NUMBER() OVER (PARTITION BY VEHICLE ORDER BY time) rownum,
loc.*
FROM loc
)
select a.*
from loc a
join loc b ON a.VEHICLE = b.VEHICLE
AND b.rownum = a.rownum + 1
AND a.location <> b.location
union
select b.*
from loc a
join loc b ON a.VEHICLE = b.VEHICLE
AND b.rownum = a.rownum + 1
AND a.location <> b.location
order by vehicle, time

querying in MYSQL according to row number

If there is a way to query according to row number ? Like:
select rownumber from table;
what i am trying to do:
I have computed the total number of rows in a table using sub queries and now I want to display only the last one.
rookie answer:
I wanted to get the 2nd highest salary. so this is what i did:
select * from gg where salary< (select max(salary) from gg) order by salary desc limit 1
This doesnot work for Nth highest salary its for 2nd highest only. AND I AM NEW TO THIS SO I JUST WANTED TO GET THE 2ND HIGHEST BY MY OWN METHOD.
Although I'm not sure why you would want to get a specific rownumer, you could do it like this:
select * from table LIMIT rownumer, 1;
Read more on this on http://dev.mysql.com/doc/refman/5.0/en/select.html
So if you want the 3rd row you'll do:
SELECT * FROM table LIMIT 3,1;
If you want the 6th-16th row you'll use:
SELECT * FROM table LIMIT 6,10
Do understand that this is only usefull to get a random row or when you add SORT BY. Otherwise you will never be sure what row will be returned...
If you want to select records with row number see this:
SELECT (#rank := #rank + 1) AS row_number
,contract_id, price1, price2, price3
FROM t1, (SELECT #rank := 0) tmp;
If you want only row number see this:
SELECT (#rank := #rank + 1) AS row_number
FROM t1, (SELECT #rank := 0) tmp
See this SQLFiddle
If you want to just limit your query to return only a specific number of rows you can use the Mysql built in LIMIT row_count OFFSET offset clause. But, If you want to select a ranking number like a row number use:
SET #rownum = 0;
Select *
FROM
(
Select *, (#rownum := #rownum+1) as rank
FROM t
) sub
Update: If you want to
I want to display only the last one.
Then you can do it this way:
SELECT *
FROM Yourquery
ORDER BY SomeField DESC --This how you determine the last one based on the order
LIMIT 1;
select * from table limit [offset,limit]
offset is the row number which you want to start,
limit is the count of rows you want to get starting from offset
so you can use it like
select * from table limit rownumber,1
There's no row number without ordering, but if you do have a sort order in mind then
SELECT * FROM thing ORDER BY ordering LIMIT 1 OFFSET your_row;

MySql: get at least N elements

How to express in a MySQL query: "get all new elements but at least N elements".
I'll try to explain better. I want to know how to make a query if I have a product list [product, price, date, new] and I want to show the user all new elements, but if there are less than N elements (e.g. 5) show also the last items. If could think in a query like:
SELECT * FROM `products` WHERE new='1' ORDER BY date DESC
but this just returns all new elements and this:
SELECT * FROM `products` WHERE new='0' ORDER BY date DESC LIMIT 5
just returns the minimum elements required.
Some examples:
If the table contains 4 new items, the query should return 4 new items and the latest non-new item
If the table contains 7 new items, the query should return just the 7 new items
If the table contains 1 new item, the query should return the new item and 4 non-new items.
Use UNION to combine two result sets.
(
SELECT *
FROM `products`
WHERE new = '1'
)
UNION
(
SELECT *
FROM `products`
ORDER BY new DESC, date DESC
LIMIT 5
) T1
ORDER BY new DESC, date DESC
The first part of the UNION returns all the new items.
The second part of the UNION returns the first five new items, or if there are fewer than 5 items, all the new items and then first 5-n old items.
The UNION removes any duplicates in the result set.
Note that this assumes that there is a unique key defined on any column or combination of columns in your table (for example, if your table has a primary key this should work).
Order also by new, not only date
SELECT * FROM `products` ORDER BY new, date DESC
I think the best you can do is a UNION of two selects: 1) all new items and 2) the minimum number of additional items. Then decide in your application logic which rows to display. SQL doesn't provide syntax to do what you're asking.
EDIT: Actually, perhaps it is possible to do this using user-defined variables. It's probably less work (for the MySQL server) just to do the UNION and have done with it, though.
select * from products order by new desc, date desc limit 5
This assumes that the "new" column will only have values of 1 or 0 in it.
SELECT *, #rownum := #rownum + 1 AS rownum
FROM products, (SELECT #rownum := 0) AS vars
ORDER BY new DESC, date DESC
HAVING rownum <= 5 OR new = 1
Pseudocode:
CREATE STOREPROCEDURO
(
#howMany INT
)
Declare #nNew INT
Declare #nOld INT
SET #nNew = (select count(*) from table where new = 1 order by date desc)
set #nOld = (select count(*) from table where old = 1 order by date desc)
set #nOld = #howMany - #nNew
if (#nOld < 0)
begin
#nOld = 0
end
(select top (#nNew) * from table where new = 1 order by date desc)
union
(select top (#nOld) * from table where new = 0 order by date desc)
</code>