Daterange histogram with SQL (MySQL) - mysql

I have a table with date ranges like the following:
start | end
2020-07-25 20:37:00 2020-07-25 20:44:00
2020-07-25 21:37:00 2020-07-25 22:44:00
2020-07-26 07:11:00 2020-07-27 10:50:00
...
At the end, I want a histogram which shows for every hour of a day how many date ranges "overlaps" each hour.
So the resulting histogram consists of 24 bars.
How do I do this in SQL for MySQL? (Side note: I'm using TypeORM, but I'm able to write plain SQL statements)
I only found solutions calculating and grouping by the length of the individual intervals with TIMESTAMPDIFF, but that's not what I want to achieve.
In future I may want to show the same histogram not per hour but per minute of a day or per day of a month and so on. But I assume that's simple to do once I get the idea of the query :)

One method is the brute force method:
with recursive hours as (
select 0 as hh
union all
select hh + 1
from hours
where hh < 23
)
select h.hh, count(t.start)
from hours h left join
t
on start >= '2020-07-25' + interval h.hh hour and
end < '2020-07-25' + interval (h.hh + 1) hour
where end < '2020-07-25' + interval 1 day and
start >= '2020-07-25'
group by h.hh
order by h.hh;

Related

Select records between a date in d-mY format and 1rst of next month, plus 1 day in Mysql

I need to select all data from a table 'followup' between followup_date and first of next month (including 1rst of next month). The format of my date in DB (obtained from an API) is d-m-Y.
The follow-up date for example is: 18-07-2020.
I have the following query:
SELECT * from followup
WHERE DATEDIFF(STR_TO_DATE(`followup_date`,'%d-%m-%Y'), DATE_FORMAT(CURDATE() + INTERVAL 1 MONTH,'%Y-%m-01'))- 1 < 0 ;
I am getting -15 days as difference and getting records correctly, including 1rst. Is the query correct and efficient and will it work correctly for all months.
Requesting suggestions from experts for improvements, if any.
You should convert your followup_date column to a DATE type. You can then make your query sargable by removing the function calls on followup_date and simply comparing it with the target date:
SELECT * from followup
WHERE `followup_date` <= DATE_FORMAT(CURDATE() + INTERVAL 1 MONTH,'%Y-%m-01')
I suspect adding one day to LAST_DAY(CURDATE()) might be more efficient:
SELECT * from followup
WHERE `followup_date` <= LAST_DAY(CURDATE()) + INTERVAL 1 DAY
Demo on SQLFiddle

SQL - Using query with date parameters with IN used for a sub-query

I have the following query which is returning nothing:
SELECT e.`value`, e.`machine_id`, e.`date_recorded`
from engine_hours e
where e.`date_recorded` >= NOW() - INTERVAL 32 DAY
AND e.`date_recorded` <= NOW() - INTERVAL 2 DAY
AND e.`machine_id` IN (SELECT m.id FROM `machines` m WHERE m.`title` = 'ABC-123')
When I break the query and sub-query into two independent queries they work fine
Question:
How can I use what the sub-query returns to the main query, alongside the date boundaries in the WHERE section of the main query.
Clarification & Better Explanation:
I get no errors.
When I run the sub-query as a query of its own ie:
SELECT m.id FROM `machines` m WHERE m.`title` = 'ABC-123'
it returns 1234 which is correct. And when I use this returned value 1234 in the main query ie:
SELECT e.`value`, e.`machine_id`, e.`date_recorded`
from engine_hours e
where e.`date_recorded` >= NOW() - INTERVAL 32 DAY
AND e.`date_recorded` <= NOW() - INTERVAL 2 DAY
AND e.`machine_id = '1234'
I get the data I am after.
I can't seem to understand what I am doing wrong.
Explaining the dates:
I want to get the data starting from 32 days ago till 2 days ago:
where e.`date_recorded` >= NOW() - INTERVAL 32 DAY
AND e.`date_recorded` <= NOW() - INTERVAL 2 DAY
ie now is 2019-03-06, 32 days ago (NOW() - INTERVAL 32 DAY) will be 2019-02-02
and similarly 2 days ago will be 2019-03-04.
The information I want should be greater than or equal to (>=) 2019-02-02 and less then or equal to (<=) 2019-03-04.
Use join
SELECT e.`value`, e.`machine_id`, e.`date_recorded`,m.id
from engine_hours e join `machines` m on e.`machine_id`=m.id
where e.`date_recorded` >= NOW() - INTERVAL 32 DAY
AND e.`date_recorded` <= NOW() - INTERVAL 2 DAY
and m.`title` = 'ABC-123'
EDITED
I've just tried it myself, it's working fine. Each query individually is correct (syntax & logic). Both queries together is also correct (syntax & logic).
So it's either:
The machine_id in the targeted rows in engine_hours is different than 1234.
The date_recorded in the targeted rows in engine_hours is not of the date range desired.
The query you posted in StackOverflow is slightly different than the one you have in your source code.
Maybe you're including the database name in the query. If so, make sure you're targeting the correct database name.
I suggest you delete the entire query in your source code and re-write it again. You probably have some hidden character(s), wrong operator used, or alike.

Dynamic due date finder in a single query

id start_date interval period
1 2018-01-22 2 month
2 2018-02-25 3 week
3 2017-11-24 3 day
4 2017-07-22 1 year
5 2018-02-25 2 week
the above is my table data sample. start_dates will be expired based on interval and period(i.e id-1 will have due date after 2 months from the start_date, id-2 will have due after 3 weeks vice versa). period is enum of (day,week,month,year). requirement is, Client can give any period of dates. let's say 25-06-2026 to 13-07-2026 like that.. I have to return the ids whose due dates falls under that period.I hope i made my question clear.
I am using mysql 5.7. I found a way to achieve this with recursive CTE's.(not available in mysql 5.7). and there is a way to achieve this by populating virtual records by using inline sub queries along with unions but its a performance killer and we can't do populate virtual records every time a client request comes.(like given in the link Generating a series of dates) I have reached a point to get results for a single date which is very easy. Below is my query.
SELECT b.*
FROM (SELECT a.*,
CASE
WHEN period = 'week' THEN MOD(Datediff('2018-07-22', start_date), 7 * intervals)
WHEN period = 'month'
AND Day('2018-07-22') = Day(start_date)
AND MOD(Period_diff(201807, Extract(YEAR_MONTH FROM start_date)), intervals) = 0 THEN 0
WHEN period = 'year'
AND Day('2018-07-22') = Day(start_date)
AND MOD(Period_diff(201807, Extract(
YEAR_MONTH FROM start_date)) / 12,
intervals) = 0 THEN 0
WHEN period = 'day' THEN MOD(Datediff('2018-07-22', start_date) , intervals)
end filters
FROM kml_subs a)b
WHERE b.filters = 0;
But I need to do this for a period of dates not a single date. Any suggestions or solutions will be much appreciated.
My desired result shoud be like..
if i give two dates.say 2030-05-21 & 2030-05-27. due dates falls under those 6 dates between(2030-05-21 & 2030-05-27) will be shown in the result.
id
1
4
My question is different from Using DATE_ADD with a Column Name as the Interval Value . I am expecting a dynamic way to check due dates based on start_date
Thanks, Kannan
In MySQL, it would seem that a query along these lines would suffice. (Almost) everything else could and should be handled in application level code...
SELECT *
, CASE my_period WHEN 'day' THEN start_date + INTERVAL my_interval DAY
WHEN 'week' THEN start_date + INTERVAL my_interval WEEK
WHEN 'month' THEN start_date + INTERVAL my_interval MONTH
WHEN 'year' THEN start_date + INTERVAL my_interval YEAR
END due_date
FROM my_table;

Mysql time range query using extract

I'm trying to get an mysql query similar to date_trunc in psql.
Unfortunate Mysql do not have date_trunc function and I found I can use extract instead in Mysql.
What I want to do is write a script which i will run let say 10 minutes past each hour but I want to only select data from begin of an hour till end of this hour.
For example I will run script 12:10 and I want to display data from 11:00:00 till 11:59:59.
In PSQL query would look like that:
SELECT *
FROM data
WHERE time > ( date_trunc('hour',now()) - interval '1 hour' )
AND time <= ( date_trunc('hour',now()) ) ORDER BY time;
I was trying to use extract in similar fashion but I have no rows returned or error :/
Query below returns for example some narrowed data but it's like 2 hours each day from day one when database was started not last hour only:
SELECT *
FROM data
WHERE extract(hour from cr_date) between extract(hour from now()) - interval 1 hour)
AND extract(hour from now())
ORDER BY cr_date;
Any ideas how this can be achieved? or what I'm doing wrong in this query?
Hour is only an integer, so it's finding any matches between , for example, 9 and 10, regardless of the date.
I would recommend
select * FROM data
where cr_date >= date(now()) + INTERVAL hour(now())-1 HOUR
and cr_date <= date(now()) + INTERVAL hour(now()) HOUR
date(now()) returns midnight, and hour(now()) returns the number of hours since midnight
so, at 11:10 am, it should result in a results between midnight + 10 hours (10 am) and midnight + 11 hours (11 am)

Last n weekdays in sql where clausel

We are using MySQL as our database to store messages with timestamps. Is it possible to create a query that returns messages of the last n weekdays?
I.e. if n is 4 and today is Tuesday, I want messages from this weeks Monday, last weeks Friday, last weeks Thursday and last weeks Wednesday .
If you want to do this directly with mysql it would be a little complicated. As Vatev recommended you should calculate date star date in advance, but if you really want to do this, you'll probably need following functions:
ADD_DATE, with INTERVAL -N WEEKS
FLOOR, in C int/int would do just fine
MOD, a % b :)
WEEKDAY
First of all you need should count how many weeks you should go back, that's easy... For you one week = 5 days, that means
weeks = FLOOR(days / 5)
We've taken care of weeks, so we'll now have to work with the rest:
rest = days MOD 5
Now we have two cases, weekend has occurred or no, for the case that there wasn't weekend days are good. We have to add 2 days to skip it. The weekend occurred if (WEEKDAY(now) - rest) < 0
rest = IF( (WEEKDAY(now) - rest) < 0, rest + 2, rest)
And now we can build it to one par (let's assume you have {days} and {rest} pre-calculated):
WHERE date >= ADD_DATE(
ADD_DATE (
{now},
INTERVAL -IF( (WEEKDAY({now}) - {rest}) < 0, {rest} + 2, {rest}) DAYS,
),
INTERVAL -FLOOR({days} / 5) WEEKS
)
The best i can come up with is calculating the start date ({start_date} in the query) in the language of your choice and then running something like this:
SELECT some_things
FROM your_table
WHERE
WEEKDAY(time_column) < 5
AND time_column >= {start_date}
ORDER BY time_column DESC
You can also make a stored function to calculate 'the date x week days ago' and use it for {start_date}.
Have you tried something like this?
SELECT columns
FROM table
WHERE datediff(column_with_timestamp,NOW()) > n
Where N is as you defined above, the number of days you're looking for.
COL >= date_sub( NOW(), interval 1 week) and
weekday( COL ) < 5
date_sub is to seek rows created last week
weekday is to exclude sunday or saturday