I'm trying to get a report in MySQL for consecutive days based on activity recorded. I have the start date&time and end date&time of a given status. My goal is to receive a report in a form:
Status|Date|Sum of activity
The problem that I've encountered is that some activities start i.e. 2019-12-12 18:21:12 and ends the next day 2019-12-13 03:21:12. Is there a way to let's say split the result for one date until 23:59:59 and add the rest of time to the following day? So far I have a code below, but it just sums the timestampdiff.
USE db;
SELECT
table1.status,
left(table1.start_time, 7) ' Date',
sec_to_time(
sum(
timestampdiff(
second,
table1.start_time,
(
case when table1.end_time is null then now() else table1.end_time end
)
)
)
) 'Sum of activity'
FROM
table1
GROUP by 1,2
Update : Let me clarify a bit my question. I have some activities that take for example 36 hours, starting on 2019-12-20 and ending on 2019-12-22. I need a composed monthly report with each day in the month selected from start_time, so for the example described above (36h over 3 days) I would like to get:
Activity1|2019-12-20|3h
Activity1|2019-12-21|24h
Activity1|2019-12-22|9h
Update2: Thank you for the 2nd update,but the proposed code works only for the first record in the dataset (for more records the time is not summed up) and doesn't take into account the activity type. I will provide more data maybe it will help:
Activity start_time end_time
1048 2019-12-27 06:42:51 2019-12-27 07:11:42
1048 2019-12-29 07:07:11 2019-12-29 07:08:59
1048 2019-12-29 07:09:19 2019-12-29 07:21:10
2066 2019-12-25 07:08:00 2019-12-25 19:01:17
2066 2019-12-25 19:01:17 2019-12-26 06:55:15
2066 2019-12-26 06:55:15 2019-12-26 18:20:51
You can use date() function :
select status, date(start_time) as date, count(*) as "Sum of activities"
from table1
group by status, date(start_time);
Demo
Update (depending on your comment): Try to use
select status, date(start_time) as date,
sec_to_time(sum(timestampdiff(second,
start_time,
(case
when end_time is null then
now()
else
end_time
end))))
as "Sum of activities"
from table1
group by status, date(start_time);
Update2 : To accomplish the last mentioned duty, need to generate rows firstly :
select date1,
extract( hour from
sec_to_time(
sum(case
when date1 = date(start_time) then
timestampdiff(second,start_time,date2)
when date1 = date(end_time) then
timestampdiff(second,date1,end_time)
else
timestampdiff(second,date1,date2)
end
)) ) as "Time Difference as hour"
from
(
select #cr := #cr + 1 as rn,
date_sub(date(end_time), interval date(end_time)-date(start_time) - #cr + 1 day) as date1,
date_sub(date(end_time), interval date(end_time)-date(start_time) - #cr day) as date2,
start_time, end_time
from information_schema.tables c1
cross join ( select #cr := 0 ) r
cross join table1 t
where #cr < date(end_time)- date(start_time)+1
) q
group by date1;
Demo 2
removing extract( hour from ) part you can get the whole difference upto second precision.
Related
I have a single line in MySQL table: volunteers
user_id | start_date | end_date
11122 | 2017-04-20 | 2018-02-17
How can I find how many times the 3rd day or 24th day of a month appears? (i.e. 2017-05-03, 2017-06-03, 2017-12-24, 2018-01-24) I'm trying to get to the following count:
Sample Output:
user_id | number_of_third_day | number_of_twenty_fourth_day
11122 | 10 | 10
I look at the documentation online to see if there is a way I can say (pseudo):
SELECT
day, COUNT(*)
FROM volunteers
WHERE day(between(start_date, end_date)) in (3,24)
I tried to create a calendar table to no avail, but I would try to get the days, GROUP BY day, and COUNT(*) times that day appears in the range
WITH calendar AS (
SELECT start_date AS date
FROM volunteers
UNION ALL
SELECT DATE_ADD(start_date, INTERVAL 1 DAY) as date
FROM volunteers
WHERE DATE_ADD(start_date, INTERVAL 1 DAY) <= end_date
)
SELECT date FROM calendar;
Thanks for any help!
This one is more optimized since I generate date range by months not days as other questions, so its faster
WITH RECURSIVE cte AS
(
SELECT user_id, DATE_FORMAT(start_date, '%Y-%m-03') as third_day,
DATE_FORMAT(start_date, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM table1
UNION ALL
SELECT user_id,
DATE_FORMAT(third_day + INTERVAL 1 MONTH, '%Y-%m-03') as third_day,
DATE_FORMAT(twenty_fourth_day + INTERVAL 1 MONTH, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM cte
WHERE third_day + INTERVAL 1 MONTH <= end_date
)
SELECT user_id,
SUM(CASE WHEN third_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_third_day,
SUM(CASE WHEN twenty_fourth_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_twenty_fourth_day
FROM cte
GROUP BY user_id;
Demo here
A dynamic approach is.
but creating the dateranges, takes a lot of time, so you should have a date table to get the dates
CREATE TABLE table1
(`user_id` int, `start_date` varchar(10), `end_date` varchar(10))
;
INSERT INTO table1
(`user_id`, `start_date`, `end_date`)
VALUES
(11122, '2017-04-20', '2018-02-17')
,(11123, '2019-04-20', '2020-02-17')
;
Records: 2 Duplicates: 0 Warnings: 0
WITH RECURSIVE cte AS (
SELECT
user_id,
`start_date` as date_run ,
`end_date`
FROM table1
UNION ALL
SELECT
user_id,
DATE_ADD(cte.date_run, INTERVAL 1 DAY),
end_date
FROM cte
WHERE DATE_ADD(date_run, INTERVAL 1 DAY) <= end_date
)SELECT user_id,
SUM(DAYOFMONTH(date_run) = 3) as day_3th,
SUM(DAYOFMONTH(date_run) = 24) as day_24th
FROM cte
GROUP BY user_id
user_id
day_3th
day_24th
11122
10
10
11123
10
10
fiddle
In last MySQL version you can use recursion:
-- get list of all dates in interval
with recursive dates(d) as (
select '2017-04-20'
union all
select date_add(d, interval 1 day) from dates where d < '2018-02-17'
) select
-- calculate
sum(day(d) = 10) days_10,
sum(day(d) = 24) days_24
from dates
-- filter 10 & 24 days
where day(d) = 10 or day(d) = 24;
https://sqlize.online/sql/mysql80/c00eb7de69d011a85502fa538d64d22c/
As long as you are looking for days that occur in every month (so not the 29th or beyond), this is just straightforward math. The number of whole calendar months between two dates (exclusive) is:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
Then add one if the start month includes the target day and one if the end month includes it:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
+ (day(start_date) <= 3) + (day(end_date) >= 3)
Is there a way to group a sequence of rows in SQL (MySQL 5.1.73).
Let me explain, I have a query that gives this:
hour
start_date
end_date
10
2022-02-01 10:11:18
2022-02-01 10:50:18
11
2022-02-01 11:30:31
2022-02-01 11:38:12
13
2022-02-17 13:55:09
2022-02-17 13:58:38
14
2022-02-17 14:51:09
2022-02-17 14:57:59
And I would like to convert it to this:
hour
start_date
end_date
10
2022-02-01 10:11:18
2022-02-01 11:38:12
13
2022-02-17 13:55:09
2022-02-17 14:57:59
Indeed, I would like to group all the lines whose hours follow each other.
My request is a grouping in hours, like this :
SELECT hour( date ) as hour, MIN(date) as start_date , MAX(date) as end_date
FROM test_tbl
GROUP BY hour( date ) , date( date )
order by date, hour( date ) ;
But after doing this query, I would like to group the lines whose hours follow each other (10,11 => 10)...
EDIT: the following answer only works with MySQL version 8+
with tbl_by_hour as (
SELECT hour( date ) as hour, MIN(date) as start_date , MAX(date) as end_date
FROM test_tbl
GROUP BY hour( date ) , date( date )
order by date, hour( date )
)
select
min(hour) as hour,
min(start_date) as start_date,
max(end_date) as end_date
from (
select tab1.*,
sum(case when prev_hour is null or prev_hour = hour - 1 then 0 else 1 end) over(order by hour) grp
from (
select hour, start_date, end_date, lag(hour) over(order by hour) prev_hour from tbl_by_hour
) as tab1
) as tab2
group by grp
You can probably do something like this:
SELECT MIN(hours), dates, MIN(start_date), MAX(end_date), tn
FROM
(SELECT *,
CEIL(rownum/5) AS tn
FROM
(SELECT *,
CASE WHEN dates=#dt
AND hours=#hr+1
THEN #rn := #rn+1
WHEN dates=#dt
AND hours > #hr+1
THEN #rn := #rn+20
ELSE #rn := 1
END AS rownum,
#dt := dates,
#hr := hours
FROM
(SELECT hour(date) as hours, date(date) dates,
MIN(date) as start_date , MAX(date) as end_date
FROM test_tbl t
GROUP BY dates, hours) v
CROSS JOIN (SELECT #rn := 0, #dt := NULL, #hr := 0) r
ORDER BY dates, hours) s
) w
GROUP BY dates, tn;
I took your original query as base then made it as subquery.
Then I CROSS JOIN with a subquery of variables where I'm attempting to generate a custom row numbering. The conditions of the row number are:
If it's on the same date and the next hour increment from previous is +1 then continue the numbering.
If it's on the same date and the next hour increment from previous more than +1 then pick-up the last number and increment it by +20.
Repeat the row numbering sequence if the date is different.
After generating the row numbering, I convert to subquery then divide the row numbering by 5 and use ceiling (CIEL) function to somehow make them the same, effectively identifying (assuming) these rows with same CIEL(rownum/5) result as one group - this is where I felt it's not really convincing but it works anyhow.
Lastly, I convert that to a subquery again and did the whole MIN(hours), dates, MIN(start_date), MAX(end_date), tn with GROUP BY dates, tn.
It's not a convincing solution because the final operation (generating of the tn column) is based on creativity and not something certain. I usually prefer a solution that covers all the possible scenarios with something concrete rather than creative. However, I did some extensive tests on the current query with more data variation and so far it's returning good results. Also, I do notice that you said your MySQL version is 5.1+ so, I'm not really sure if this particular operation will work. Version 5.5+ is probably the lowest version of MySQL fiddle that is available online.
Here's a demo fiddle
I am very new to SQL and I need to write a query that selects data for a specific part. However, It should select only the part that is the most recent(given by date) and anything that is only 30 days prior to it. Please consider the table below:
PartID | Part_NAME | DATE
-----------------------------
1 AAA 6/16/2015
2 BBB 6/15/2015
3 AAA 6/11/2015
4 AAA 1/1/2008
I need a query that gives me:
PartID | Part_NAME | DATE
-----------------------------
1 AAA 6/16/2015
3 AAA 6/11/2015
I have tried:
select * from ( select * from sales_table where Part_NAME = 'AAA') where DATE BETWEEN (max(DATE) and (max(DATE)-30))
I have read some articles saying that I cannot use WHERE and functions like max() together and advised me to use group by or having but it didn't work for me as well. Thank you.
IF you want data from the last 30 days of the current day, you can do :
SELECT *
FROM sales_table
WHERE
[DATE] >= DATEADD(DAY, -30,GETDATE())
AND [DATE] <= GETDATE()
AND Part_NAME = 'AAA'
IF you want data from the last 30 days from the last date of sale of each Part_NAME (this will take the max recorded date of sale for each Part_NAME and get the last 30 days records of each one of them.)
SELECT *
FROM (
SELECT *,
MAX([DATE]) OVER(PARTITION BY Part_NAME ORDER BY PartID) AS RecentDate
FROM sales_table
) D
WHERE
[DATE] >= DATEADD(DAY, -30, RecentDate)
AND [DATE] <= RecentDate
AND Part_NAME = 'AAA'
You can accomplish by using datediff and getdate() and a subquery.
SELECT * FROM (
SELECT *,DATEDIFF(DD,[DATE],GETDATE()) AS DAYSBETWEEN FROM sales_table
) AS X
WHERE DAYSBETWEEN <= 30
If you want data from the last 30 days, it would be:
select st.*
from sales_table st join
(select top (1) st2.*
from sales_table st2
order by st2.date desc
) st2
on st2.part_name = st.part_name and
st.date >= dateadd(day, -30, cast(getdate() as date));
I've a table and i want that data is interval by 4 or, when i'm using modulo the record is not that i expected, PFB `
SELECT (DATE_FORMAT(subscribed_from, '%Y-%m')) AS date_ FROM subscription
WHERE operator = 'tim'
AND DATE_FORMAT(subscribed_from, '%Y-%m-%d') BETWEEN '2013-01-01' AND '2014-12-31'
GROUP BY (DATE_FORMAT(subscribed_from, '%Y-%m'));
it will show record like this
2013-01
2013-02
2013-03
2013-04
2013-05
2013-06
2013-07
2013-08
2013-09
i want take only data interval by 4, this below is record that i expected.
2013-01
2013-05
2013-09
2014-02
and also for interval by 2, this below record is that i expected
2013-01
2013-03
2013-05
2013-07
2013-09
if i using modulo % 2 it will start from 2013-01 and jump by 2, but the problem if the where range i want to start from 2013-02, 02 it self not showing on the result. so if the where clause the month start from 2 it will given the interval such as 2,4,6,8,10,12
SELECT date_, SUM(the_metric_you_want_to_aggregate)
FROM (
SELECT 4*FLOOR(
(DATE_FORMAT(subscribed_from, '%Y%m') - 201301)
/4) AS date_,
the_metric_you_want_to_aggregate
FROM subscription
WHERE operator = 'tim'
AND subscribed_from BETWEEN 20130101000000 AND 201412315959
) AS ilv
GROUP BY date_
(where 201301 is the year/month start of the range you are selecting by - assuming that is the reference for the 4-month aggregation)
Note that enclosing column references in functions (...DATE_FORMAT(subscribed_from, '%Y-%m-%d') BETWEEN...) prevents the use of indexes.
You have to use variables. Here is sample for interval by 4.
SET #row_number:=0;
SELECT date_ from (
SELECT (DATE_FORMAT(subscribed_from, '%Y-%m')) AS date_,#row_number:=#row_number+1 FROM subscription
WHERE operator = 'tim' AND DATE_FORMAT(subscribed_from, '%Y-%m-%d') BETWEEN '2013-01-01' AND '2014-12-31'
GROUP BY (DATE_FORMAT(subscribed_from, '%Y-%m'))
) as tbl where #row_number % 4=0;
let says i'm using this method to generate the intevals, but i want the start number is from my input, let says it start from 4 and if the condition put %4 should be the output is 4, 8 ,12 ....
enter code here
SET #row:=0;
SELECT *
FROM (
SELECT
#row := #row +1 AS rownum
FROM (
SELECT #row) r, subscription
) ranked
WHERE rownum %4 = 1
I have a table tbl_subscriptions and columns like this "id, user_name, join_date(date)", I want to select the users before 7 days every month based on join_date so that I can send them notifications to continue their subscription for the next month. I have records like this
1, user1, 2014-05-02
2, user2, 2014-05-04
3, user3, 2014-06-12
4, user4, 2014-06-20
4, user5, 2014-07-24
If today is 2014-07-28, then I want to get records 1 and 2. I tried below query
SELECT *,
datediff( date_format(date, '2014-07-%d'), now() ) as daysLeft
FROM tbl_subscriptions
HAVING daysLeft >= 0
AND daysLeft < 7
the problem with above sql is that it is selecting the record of the current month only, plz suggest any better query.
Does this do what you want?
SELECT s.*, datediff(date, curdate()) as daysLeft
FROM tbl_subscriptions s
WHERE date >= curdate() and date < curdate() + interval 7 day;
EDIT:
I see. These are recurrent subscriptions and you want to find the next ones. The following logic should work:
select s.*,
(case when day(date) >= day('2014-07-28')
then day(date) - day('2014-07-28')
else day(date) + day(last_day('2014-07-28')) - day('2014-07-28')
end) as diff
from tbl_subscriptions s
having diff <= 7;
Here is the SQL Fiddle.
Ok, first of all I dont know what is subscription renewal period. And idea of only checking date (and not the whole period) doesnt make sense to me.
But this will get you your required output.
SELECT *,
day(date) days,
day(last_day('2014-07-28')) as lastday,
day('2014-07-28') today, day(last_day('2014-07-28'))-day('2014-07-28') as diff
FROM tbl_subscriptions
having days <= (7-diff) or (days > today and days <= today+7)
And here goes the demo (schema thanks to one of the deleted answer) ->
http://sqlfiddle.com/#!2/3cc4f