MySQL date difference from dates in one column - mysql

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!

Related

Get Data According to Group by date field

Here is my table
Which have field type which means 1 is for income and 2 is for expense
Now requirement is for example in table there is two transaction made on 2-10-2018 so i want data as following
Expected Output
id created_date total_amount
1 1-10-18 10
2 2-10-18 20(It calculates all only income transaction made on 2nd date)
3 3-10-18 10
and so on...
it will return an new field which contains only incom transaction made on perticulur day
What i had try is
SELECT * FROM `transaction`WHERE type = 1 ORDER BY created_date ASC
UNION
SELECT()
//But it wont work
SELECT created_date,amount,status FROM
(
SELECT COUNT(amount) AS totalTrans FROM transaction WHERE created_date = created_date
) x
transaction
You can Also See Schema HERE http://sqlfiddle.com/#!9/6983b9
You can Count() the total number of expense transactions using conditional function If(), on a group of created_date.
Similarly, you can Sum() the amount of expense done using If(), on a created_date.
Try the following:
SELECT
`created_date`,
SUM(IF (`type` = 2, `amount`, 0)) AS total_expense_amount,
COUNT(IF (`type` = 2, `id`, NULL)) AS expense_count
FROM
`transaction`
GROUP BY `created_date`
ORDER BY `created_date` ASC
Do you just want a WHERE clause?
SELECT t.created_date, SUM(amount) as total_amount
FROM transaction t
WHERE type = 2
GROUP BY t.created_date
ORDER BY created_date ASC ;

sql request to retrieve each day max, min first and last record

I would like to get the min, and max, the first and the last record in my database mysql.
I'm looking for a high-performance query that takes no more than 15 minutes.
I have tested :
SELECT
MIN(low),
MAX(high),
CONCAT(YEAR(date), "-", WEEK(date)) AS myweek,
(select open from prices m where WEEK(m.date)=WEEK(prices.date) order by date LIMIT 1) as first_open,
(select close from prices m where WEEK(m.date)=WEEK(prices.date) order by date desc LIMIT 1) as last_close
FROM prices
GROUP BY myweek
ORDER BY date;
But i have a error :
Erreur dans la requête (1055): Expression #4 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'siteinfo.prices.date' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
I can not correct this error because I do not have access to conf files and I do not have user superadmin.
I have tested too :
select
DATE_FORMAT(date, "%Y" "%m" "%d") as datetime,
(select open from prices m where m.date like CONCAT(YEAR(datetime), "-[0]", Month(datetime), "-[0]", DAY(datetime), "%") and fk_cryptoid = 'f2a0ba90-93df-11e8-af1b-5968de90d63b' order by date ASC LIMIT 1) as open,
(select close from prices m where m.date like CONCAT(YEAR(datetime), "-[0]", Month(datetime), "-[0]", DAY(datetime), "%") and fk_cryptoid = 'f2a0ba90-93df-11e8-af1b-5968de90d63b' order by date DESC LIMIT 1) as close,
min(low) as low,
max(high) as high
from prices
where fk_cryptoid = 'f2a0ba90-93df-11e8-af1b-5968de90d63b'
GROUP BY datetime;
but CONCAT() does not return the day and the month with a zero in addition: 2018-1-2 and not 2018-01-02. Then this request takes too much time.
Model for table Prices :
id int(11) Incrément automatique
close double NULL
open double NULL
low double NULL
high double NULL
date datetime NULL
createdAt datetime
updatedAt datetime
fk_cryptoid char(36) NULL
If it works for you, then I would suggest the group_concat()/substring_index() trick:
SELECT MIN(low), MAX(high),
CONCAT(YEAR(date), '-', WEEK(date)) AS myweek,
SUBSTRING_INDEX(GROUP_CONCAT(open ORDER BY date), ',', 1) as first_open,
SUBSTRING_INDEX(GROUP_CONCAT(close ORDER BY date DESC), ',', 1) as last_close
FROM prices
GROUP BY myweek
ORDER BY date;
Notes:
This first_open and last_close columns will be strings.
The assumption is that open and close do not have commas. If they do, use a different separator.
GROUP_CONCAT() has a default internal limit of about 1,000 bytes. You can adjust that using server parameters.

Find closest datetime to specified datetime in mysql query

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.

SQL query that finds a negative change between two rows with the same name field

I have a single table with rows like this: (Date, Score, Name)
The Date field has two possible dates, and it's possible that a Name value will appear under only one date (if that name was recently added or removed).
I'm looking to get a table with rows like this: (Delta, Name), where delta is the score change for each name between the earlier and later dates. In addition, only a negative change interests me, so if Delta>=0, it shouldn't appear in the output table at all.
My main challenge for me is calculating the Delta field.
As stated in the title, it should be an SQL query.
Thanks in advance for any help!
I assumed that each name can have it's own start/end dates. It can be simplified significantly if there are only two possible dates for the entire table.
I tried this out in SQL Fiddle here
SELECT (score_end - score_start) delta, name_start
FROM
( SELECT date date_start, score score_start, name name_start
FROM t t
WHERE NOT EXISTS
( SELECT 1
FROM t x
WHERE x.date < t.date
AND x.name = t.name
)
) AS start_date_t
JOIN
( SELECT date date_end, score score_end, name name_end
FROM t t
WHERE NOT EXISTS
( SELECT 1
FROM t x
WHERE x.date > t.date
AND x.name = t.name
)
) end_date_t ON start_date_t.name_start = end_date_t.name_end
WHERE score_end-score_start < 0
lets say you have a table with date_value, sum_value
Then it should be something like that:
select t.date_value,sum_value,
sum_value - COALESCE((
select top 1 sum_value
from tmp_num
where date_value > t.date_value
order by date_value
),0) as sum_change
from tmp_num as t
order by t.date_value
The following uses a "trick" in MySQL that I don't really like using, because it turns the score into a string and then back into a number. But, it is an easy way to get what you want:
select t.name, (lastscore - firstscore) as diff
from (select t.name,
substring_index(group_concat(score order by date asc), ',', 1) as firstscore,
substring_index(group_concat(score order by date desc), ',', 1) as lastscore
from table t
group by t.name
) t
where lastscore - firstscore < 0;
If MySQL supported window functions, such tricks wouldn't be necessary.

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