Find closest datetime to specified datetime in mysql query - mysql

I am trying to find a datetime value in a mysql database that is the closest match to a datetime that i specify, i am having some trouble.
The following pseudo code is what i want to achieve:
SELECT one FROM table WHERE datetimefield is closest to "2014-12-10 09:45:00" LIMIT 1

The key idea is to use order by and limit:
If you want the closest one before:
SELECT one
FROM table
WHERE datetimefield <= '2014-12-10 09:45:00'
ORDER BY datetimefield DESC
LIMIT 1;
If you want the closest, in either direction, then use TIMESTAMPDIFF():
ORDER BY abs(TIMESTAMPDIFF(second, datetimefield, '2014-12-10 09:45:00'))
LIMIT 1

Using abs() prevents using a datetimefield index. I propose to have one select for the closest before and one select for the closest after, both using the index, and picking the closest of them afterwards:
create table `table` (datetimefield datetime key, one varchar(99));
insert into `table` values
('2014-06-01', 'a'), ('2014-12-01', 'b'),
('2015-01-01', 'c'), ('2015-02-01', 'd');
set #d = '2014-12-10 09:45:00';
select * from
(
( select *, TIMESTAMPDIFF(SECOND, #d, datetimefield) as diff
from `table` where datetimefield >= #d
order by datetimefield asc limit 1
)
union
( select *, TIMESTAMPDIFF(SECOND, datetimefield, #d) as diff
from `table` where datetimefield < #d
order by datetimefield desc limit 1
)
) x
order by diff
limit 1;
http://sqlfiddle.com/#!2/bddb4/1

Use ABS()
SELECT one FROM table
ORDER BY ABS(`datetimefield` - '2014-12-10 09:45:00') LIMIT 1
This will return the row with lowest difference, that is closest.

Related

MySQL date difference from dates in one column

I have a table "nasypka", with structure:
ID|datetime |height|refilled
1 |2022-09-01 12:00|101 |1
2 |2022-09-01 12:01|96 |0
3 |2022-09-01 12:02|50 |0
4 |2022-09-01 12:03|10 |0
...
50|2022-09-05 17:04|105 |1
51|2022-09-05 17:05|104 |0
...
80|2022-09-15 10:04|99 |1
81|2022-09-15 10:05|98 |0
This table holds data about amount of coal in reservoir (height in centimeters) of coal boiler. I want to know time (date) difference between refillements of coal in reservoir. Moment of refilling is marked by (boolean) "1" in "refilled" column.
ID column does not have to be contiguous.
Result will show how long the reservoir lasted to fill the boiler with coal. Expected result should be:
begin_date |end_date |difference
2022-09-01 12:00|2022-09-05 17:04|calculated difference in some format or in hours
2022-09-05 17:04|2022-09-15 10:04|calculated difference in some format or in hours
...
(limited to last X selected periods)
It seems that my hosting have MySQL version 5.5.59 EDIT: webhosting confirmed that, also offered migration to v5.7 or v8.0
I tried to google out any solution that I could modify to my case, but even that I found something similar, I was unable to modify it. I am not skilled in SQL. :-(
Meanwhile I realised few things:
SELECT (SELECT datetime FROM nasypka WHERE refilled=1 AND (datetime<(SELECT datetime FROM nasypka WHERE refilled=1 ORDER BY datetime DESC LIMIT 1)) ORDER BY datetime DESC LIMIT 1 OFFSET 0) AS zacatek
, (SELECT datetime FROM nasypka WHERE refilled=1 ORDER BY datetime DESC LIMIT 1 OFFSET 0) AS konec
, TIMESTAMPDIFF(hour,(SELECT datetime FROM nasypka WHERE refilled=1 AND (datetime<(SELECT datetime FROM nasypka WHERE refilled=1 ORDER BY datetime DESC LIMIT 1)) ORDER BY datetime DESC LIMIT 1 OFFSET 0),(SELECT datetime FROM nasypka WHERE refilled=1 ORDER BY datetime DESC LIMIT 1 OFFSET 0)) AS vydrz_hodin
FROM nasypka
WHERE refilled=1
ORDER BY datetime DESC
LIMIT 1;
This works fine, throws one row. Nasypka is the table name. The only thing here I need is to loop it and shift OFFSET value. I tried REPEAT-UNTIL sample codes, WHILE loop sample code, but nothing work in MySQL 5.5 I have at webhosting.
So I assumed it is a bit problem with that old version of MySQL. I contacted helpdesk od webhosting and got prompt reply, that I have MySQL 5.5 and if I want, I can be migrated to v5.7 or v8.0. Migration means I have to backup all tables and databases, then delete them, then I will be migrated and then I have to recreate structures and restore data. I have to google out, what else would it mean for me, if previous codes/queries (i have in PHP) will be the same, I assume yes, v8.0 will be backwards compatible, especially for my simple queries and the only code I have to change is mysql server address in "mysqli_connect".
Without 'modern' functionality of window functions (nearly two decades of support outside of MySQL), this will be slow.
First, generate an id for each 'group' of rows, based on the event flag in refilled.
I used a correlated sub-query
Then aggregate as usual.
SELECT
group_id,
MIN(date),
MAX(date),
TIMEDIFF(MIN(date), MAX(date))
FROM
(
SELECT
*,
(
SELECT
SUM(refilled)
FROM
your_table AS lookup
WHERE
lookup.datetime <= your_table.datetime
)
AS group_id
FROM
your_table
)
AS grouped
GROUP BY
group_id
ORDER BY
group_id
The max date will be the last date in the group, not the first date of the subsequent group. If that's needed, you need yet another correlated sub-query...
SELECT
group_id,
MIN(date),
MAX(end_date),
TIMEDIFF(MIN(date), MAX(end_date))
FROM
(
SELECT
*,
(
SELECT
COALESCE(MIN(lookup.date), your_table.date)
FROM
your_table AS lookup
WHERE
lookup.date > your_table.date
)
AS end_date,
(
SELECT
SUM(refilled)
FROM
your_table AS lookup
WHERE
lookup.datetime <= your_table.datetime
)
AS group_id
FROM
your_table
)
AS grouped
GROUP BY
group_id
ORDER BY
group_id
This is a working solution from #MatBailie [MatBailie]1
and I want to explicitly post it here for a case, that dbfiddle.uk/qCs4xwqT won't work, so other should find solution easily:
CREATE TABLE your_table (
id INT,
date TIMESTAMP,
height INT,
refilled BOOLEAN
)
INSERT INTO
your_table
VALUES
(1 , '2022-09-01 12:00', 101, 1),
(2 , '2022-09-01 12:01', 96 , 0),
(3 , '2022-09-01 12:02', 50 , 0),
(4 , '2022-09-01 12:03', 10 , 0),
(50, '2022-09-05 17:04', 105, 1),
(51, '2022-09-05 17:05', 104, 0),
(80, '2022-09-15 10:04', 99 , 1),
(81, '2022-09-15 10:05', 98 , 0)
SELECT
start_date,
end_date,
TIMEDIFF(end_date, start_date) AS difference
FROM
(
SELECT
date AS start_date,
(
SELECT date
FROM your_table AS lookup
WHERE date > your_table.date
ORDER BY refilled DESC, date
LIMIT 1
)
AS end_date
FROM
your_table
WHERE
refilled=1
)
AS windows
ORDER BY
start_date
TIMEDIFF can be changed to
TIMESTAMPDIFF(hour,start_datee, end_date) AS difference
Thank you, MatBailie!

MySQL Nested Select Query?

Ok, so I have the following query:
SELECT MIN(`date`), `player_name`
FROM `player_playtime`
GROUP BY `player_name`
I then need to use this result inside the following query:
SELECT DATE(`date`) , COUNT(DISTINCT `player_name`)
FROM `player_playtime /*Use previous query result here*/`
GROUP BY DATE( `date`) DESC LIMIT 60
How would I go about doing this?
You just need to write the first query as a subquery (derived table), inside parentheses, pick an alias for it (t below) and alias the columns as well.
The DISTINCT can also be safely removed as the internal GROUP BY makes it redundant:
SELECT DATE(`date`) AS `date` , COUNT(`player_name`) AS `player_count`
FROM (
SELECT MIN(`date`) AS `date`, `player_name`
FROM `player_playtime`
GROUP BY `player_name`
) AS t
GROUP BY DATE( `date`) DESC LIMIT 60 ;
Since the COUNT is now obvious that is only counting rows of the derived table, you can replace it with COUNT(*) and further simplify the query:
SELECT t.date , COUNT(*) AS player_count
FROM (
SELECT DATE(MIN(`date`)) AS date
FROM player_playtime
GROUP BY player_name
) AS t
GROUP BY t.date DESC LIMIT 60 ;

How many different ways are there to get the second row in a SQL search?

Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1

MYSQL Query : How to get values per category?

I have huge table with millions of records that store stock values by timestamp. Structure is as below:
Stock, timestamp, value
goog,1112345,200.4
goog,112346,220.4
Apple,112343,505
Apple,112346,550
I would like to query this table by timestamp. If the timestamp matches,all corresponding stock records should be returned, if there is no record for a stock for that timestamp, the immediate previous one should be returned. In the above ex, if I query by timestamp=1112345 then the query should return 2 records:
goog,1112345,200.4
Apple,112343,505 (immediate previous record)
I have tried several different ways to write this query but no success & Im sure I'm missing something. Can someone help please.
SELECT `Stock`, `timestamp`, `value`
FROM `myTable`
WHERE `timestamp` = 1112345
UNION ALL
SELECT `Stock`, `timestamp`, `value`
FROM `myTable`
WHERE `timestamp` < 1112345
ORDER BY `timestamp` DESC
LIMIT 1
select Stock, timestamp, value from thisTbl where timestamp = ? and fill in timestamp to whatever it should be? Your demo query is available on this fiddle
I don't think there is an easy way to do this query. Here is one approach:
select tprev.*
from (select t.stock,
(select timestamp from t.stock = s.stock and timestamp <= <whatever> order by timestamp limit 1
) as prevtimestamp
from (select distinct stock
from t
) s
) s join
t tprev
on s.prevtimestamp = tprev.prevtimestamp and s.stock = t.stock
This is getting the previous or equal timestamp for the record and then joining it back in. If you have indexes on (stock, timestamp) then this may be rather fast.
Another phrasing of it uses group by:
select tprev.*
from (select t.stock,
max(timestamp) as prevtimestamp
from t
where timestamp <= YOURTIMESTAMP
group by t.stock
) s join
t tprev
on s.prevtimestamp = tprev.prevtimestamp and s.stock = t.stock

MySQL multiple order set

I have a query with an order
ORDER BY FIND_IN_SET(status, '1,5,3,4,2'), end_date ASC
but what I would like is any record with status 1 to be ordered end_date ASC and any status 2 ordered by end_date DESC and the others ordered by end_date ASC.
Can this be done? Thanks
Try this query -
SELECT * FROM table_name
ORDER BY
IF(status < 2, status, 3),
IF(status = 2, TO_DAYS(end_date) * -1, TO_DAYS(end_date))
Refining Devart's answer...
SELECT * FROM table_name
ORDER BY find_in_set(status, '1,5,3,4,2')
CASE
WHEN (status = 2) then TO_DAYS(end_date) * -1
ELSE TO_DAYS(end_date)
END
Use 2 queries to get your different sorts, then get the UNION of the queries.
SELECT * FROM (
(SELECT * FROM table WHERE status!=2
ORDER BY FIND_IN_SET(status, '1,5,3,4'), end_date ASC)
UNION
(SELECT * FROM table WHERE status=2 ORDER BY end_date DESC)
) AS table_sort ORDER BY FIND_IN_SET(status, '1,5,3,4,2')
Since you want the status 2 records last, technically you wouldn't need to SELECT on the UNION and ORDER it, but I added that in case you wanted a different sort order.