Find the longest duration during the machine is ON - sql

I have the following table in SQL Server. I would like to find the longest duration for the machine running.
Row
DateTime
Machine On
1
9/22/2022 8:20
1
2
9/22/2022 9:10
0
3
9/22/2022 10:40
1
4
9/22/2022 10:52
0
5
9/22/2022 12:30
1
6
9/22/2022 14:30
0
7
9/22/2022 15:00
1
8
9/22/2022 15:40
0
9
9/22/2022 16:25
1
10
9/22/2022 16:55
0
In the example above, the longest duration for the machine is ON is 2 hours using rows 5 and 6. What would be the best SQL statement that can provide the longest duration given a time range?
Desired Result:
60 minutes
I have looked into the LAG Function and the LEAD Function in SQL.

Here's another way that uses traditional gaps & islands methodology:
WITH src AS
(
SELECT Island, mint = MIN([Timestamp]), maxt = MAX([Timestamp])
FROM
(
SELECT [Timestamp], Island =
ROW_NUMBER() OVER (ORDER BY [Timestamp]) -
ROW_NUMBER() OVER (PARTITION BY Running ORDER BY [Timestamp])
FROM dbo.Machine_Status
) AS x GROUP BY Island
)
SELECT TOP (1) delta =
(DATEDIFF(second, mint, LEAD(mint,1) OVER (ORDER BY island)))
FROM src ORDER BY delta DESC;
Example db<>fiddle based on the sample data in your new duplicate.

If this is really your data, you can simply use INNER JOIN and DATEDIFF:
SELECT MAX(DATEDIFF(MINUTE, T1.[DateTime], T2.[DateTime]))
FROM [my_table] T1
INNER JOIN [my_table] T2
ON T1.[Row] + 1 = T2.[Row];

This is a gaps and islands problem, one option to solve it is to use a running sum that increased by 1 whenever a machine_on = 0, this will define unique groups for consecutive 1s followed by 0.
select top 1 datediff(minute, min([datetime]), max([datetime])) duration
from
(
select *,
sum(case when machine_on = 0 then 1 else 0 end) over (order by datetime desc) grp
from table_name
) T
group by grp
order by datediff(minute, min([datetime]), max([datetime])) desc
See demo

This is a classic Gaps and Islands with a little twist Adj
Example
Select Top 1
Row1 = min(row)
,Row2 = max(row)+1
,TS1 = min(TimeStamp)
,TS2 = dateadd(SECOND,max(Adj),max(TimeStamp))
,Dur = datediff(Second,min(TimeStamp),max(TimeStamp)) + max(Adj)
From (
Select *
,Grp = row_number() over( partition by Running order by TimeStamp) - row_number() over (order by timeStamp)
,Adj = case when Running=1 and lead(Running,1) over (order by timestamp) = 0 then datediff(second,TimeStamp,lead(TimeStamp,1) over (order by TimeStamp) ) else 0 end
From Machine_Status
) A
Where Running=1
Group By Grp
Order By Dur Desc
Results
Row1 Row2 TS1 TS2 Dur
8 12 2023-01-10 08:25:30.000 2023-01-10 08:28:55.000 205

Related

Query for the longest duration of consecutive TRUE [duplicate]

I have the following table in SQL Server. I would like to find the longest duration for the machine running.
Row
DateTime
Machine On
1
9/22/2022 8:20
1
2
9/22/2022 9:10
0
3
9/22/2022 10:40
1
4
9/22/2022 10:52
0
5
9/22/2022 12:30
1
6
9/22/2022 14:30
0
7
9/22/2022 15:00
1
8
9/22/2022 15:40
0
9
9/22/2022 16:25
1
10
9/22/2022 16:55
0
In the example above, the longest duration for the machine is ON is 2 hours using rows 5 and 6. What would be the best SQL statement that can provide the longest duration given a time range?
Desired Result:
60 minutes
I have looked into the LAG Function and the LEAD Function in SQL.
Here's another way that uses traditional gaps & islands methodology:
WITH src AS
(
SELECT Island, mint = MIN([Timestamp]), maxt = MAX([Timestamp])
FROM
(
SELECT [Timestamp], Island =
ROW_NUMBER() OVER (ORDER BY [Timestamp]) -
ROW_NUMBER() OVER (PARTITION BY Running ORDER BY [Timestamp])
FROM dbo.Machine_Status
) AS x GROUP BY Island
)
SELECT TOP (1) delta =
(DATEDIFF(second, mint, LEAD(mint,1) OVER (ORDER BY island)))
FROM src ORDER BY delta DESC;
Example db<>fiddle based on the sample data in your new duplicate.
If this is really your data, you can simply use INNER JOIN and DATEDIFF:
SELECT MAX(DATEDIFF(MINUTE, T1.[DateTime], T2.[DateTime]))
FROM [my_table] T1
INNER JOIN [my_table] T2
ON T1.[Row] + 1 = T2.[Row];
This is a gaps and islands problem, one option to solve it is to use a running sum that increased by 1 whenever a machine_on = 0, this will define unique groups for consecutive 1s followed by 0.
select top 1 datediff(minute, min([datetime]), max([datetime])) duration
from
(
select *,
sum(case when machine_on = 0 then 1 else 0 end) over (order by datetime desc) grp
from table_name
) T
group by grp
order by datediff(minute, min([datetime]), max([datetime])) desc
See demo
This is a classic Gaps and Islands with a little twist Adj
Example
Select Top 1
Row1 = min(row)
,Row2 = max(row)+1
,TS1 = min(TimeStamp)
,TS2 = dateadd(SECOND,max(Adj),max(TimeStamp))
,Dur = datediff(Second,min(TimeStamp),max(TimeStamp)) + max(Adj)
From (
Select *
,Grp = row_number() over( partition by Running order by TimeStamp) - row_number() over (order by timeStamp)
,Adj = case when Running=1 and lead(Running,1) over (order by timestamp) = 0 then datediff(second,TimeStamp,lead(TimeStamp,1) over (order by TimeStamp) ) else 0 end
From Machine_Status
) A
Where Running=1
Group By Grp
Order By Dur Desc
Results
Row1 Row2 TS1 TS2 Dur
8 12 2023-01-10 08:25:30.000 2023-01-10 08:28:55.000 205

SQL How to group a sequence of elements that share a chronological order

I have a set of records that consists of the start and stop dates, as the following:
ID
started
stop
1
2017-08-14
2017-10-22
2
2017-10-23
2017-12-12
3
2019-01-28
2019-02-21->
4
Some-Date
NULL
5
2020-09-08
2020-09-14
6
2020-09-15
2020-10-14
7
->2019-02-22
2019-03-18
I need to merge those sequence of dates that come in a chronological order as heighlighted which follow the rule (stop = start + one day).
The result should look like this:
ID
started
stop
1
2017-08-14
2019-03-18
2
Some-Date
NULL
3
2020-09-08
2020-10-14
My approach: creates 3 new colums and check each entry x with its previous entry (x - 1), and with its next one (x + 1), and if any of those is in between -1 , +1 then group them:
update date_table as t
set group_num_before = (select date_difference
from (SELECT g1.id, DATEDIFF(g1.start_dt, g2.stop_dt) AS date_difference
FROM date_table g1
INNER JOIN
date_table g2 ON g2.id = g1.id - 1) as groupNum
where t.id = groupNum.id),
group_num_after = (select date_difference
from (SELECT g1.id, DATEDIFF(g1.stop_dt, g2.start_dt) AS date_difference
FROM date_table g1
INNER JOIN
date_table g2 ON g2.id = g1.id + 1) as groupNum
where t.id = groupNum.id)
where true;
update date_table as g1
INNER JOIN
date_table g2 ON g2.id = g1.id
set g1.group_num = IF(g1.group_num_before in (-1, 1) OR
g1.group_num_after in (-1, 1), -1, g1.group_num_before)
where g1.A = g2.B;
This result in:
ID
started
stop
groupNum
1
2017-08-14
2017-10-22
-1
2
2017-10-23
2017-12-12
-1
3
2017-12-13
2019-02-21->
-1
4
Some-Date
NULL
null
5
2020-09-08
2020-09-14
-1
6
2020-09-15
2020-10-14
-1
7
->2019-02-22
2019-03-18
-1
However, doing this put all the sequenced records in the same group since they also come in a chronological order, and this lead to incorrect result when grouping them together.
Any idea will be appreciated
Thanks in advance.
Use LAG to see the previous stop and flag all gaps. Then count the gaps to get the group numbers.
select started, stop, count(flag) over (order by started) as grp
from
(
select
started, stop,
case when lag(stop) over (order by started) = started - interval 1 day then null else 1 end as flag
from date_table
) with_flags
order by started;
If you want to get one result row per group number, aggregate above result:
select
grp,
min(started) as started,
case when count(*) = count(stop) then max(stop) end as stop
from
(
select started, stop, count(flag) over (order by started) as grp
from
(
select
started, stop,
case when lag(stop) over (order by started) = started - interval 1 day then null else 1 end as flag
from date_table
) with_flags
) grouped
group by grp
order by grp;
Demo: https://dbfiddle.uk/jKlHZ09P

sum values based on 7-day cycle in SQL Oracle

I have dates and some value, I would like to sum values within 7-day cycle starting from the first date.
date value
01-01-2021 1
02-01-2021 1
05-01-2021 1
07-01-2021 1
10-01-2021 1
12-01-2021 1
13-01-2021 1
16-01-2021 1
18-01-2021 1
22-01-2021 1
23-01-2021 1
30-01-2021 1
this is my input data with 4 groups to see what groups will create the 7-day cycle.
It should start with first date and sum all values within 7 days after first date included.
then start a new group with next day plus anothe 7 days, 10-01 till 17-01 and then again new group from 18-01 till 25-01 and so on.
so the output will be
group1 4
group2 4
group3 3
group4 1
with match_recognize would be easy current_day < first_day + 7 as a condition for the pattern but please don't use match_recognize clause as solution !!!
One approach is a recursive CTE:
with tt as (
select dte, value, row_number() over (order by dte) as seqnum
from t
),
cte (dte, value, seqnum, firstdte) as (
select tt.dte, tt.value, tt.seqnum, tt.dte
from tt
where seqnum = 1
union all
select tt.dte, tt.value, tt.seqnum,
(case when tt.dte < cte.firstdte + interval '7' day then cte.firstdte else tt.dte end)
from cte join
tt
on tt.seqnum = cte.seqnum + 1
)
select firstdte, sum(value)
from cte
group by firstdte
order by firstdte;
This identifies the groups by the first date. You can use row_number() over (order by firstdte) if you want a number.
Here is a db<>fiddle.

Get running time from table with start / stop event datetime only

I need your help to get the total running duration per day from a table when I record only start and stop events:
id
ts
event
1
2020-12-26 09:00:00.589016
0
2
2020-12-26 10:25:00.589016
1
3
2020-12-26 19:30:45.644092
0
4
2020-12-26 22:30:00.554092
1
0 = stop event
1 = start event
The difficulty here is to compute the duration between start and stop events but also:
if a start event is the day before, include the duration between midnight and the first start event (in this example 9h)
Any idea to achieve it ?
Assuming your Times are already in a datetime_64 format as shown below:
ts event
id
1 2020-12-25 23:55:09.589016 1
2 2020-12-26 00:05:18.589016 0
3 2020-12-26 09:00:00.589016 1
4 2020-12-26 10:25:00.589016 0
5 2020-12-26 19:30:45.644092 1
6 2020-12-26 22:30:00.554092 0
You can do the following:
dfs = df.loc[df.event == 1]
dfs = dfs.rename(columns={"ts": "Start"})
dfs.reset_index(drop= True, inplace=True)
dfnd = df.loc[df.event==0]
dfnd = dfnd.rename(columns={"ts": "Stop"})
dfnd.reset_index(drop= True, inplace=True)
dfdur = dfnd.Stop - dfs.Start
Which Yields the following:
0 0 days 00:10:09
1 0 days 01:25:00
2 0 days 02:59:14.910000
For each row with event = 0 and non existing previous row of the same day with event = 1 create another row with ts at midnight of the same day.
Similarly for each row with event = 1 and non existing next row of the same day with event = 0 create another row with ts at 23:59:59.99999 of the same day.
This can be done in a CTE.
Then use window function LAG() for each row with event = 0 to get the starting time and with strftime() calculate the difference and finally aggregate on all the differences of each day:
WITH cte AS (
SELECT ts, event FROM tablename
UNION ALL
SELECT datetime(date(t.ts)), 1
FROM tablename t
WHERE event = 0 AND NOT EXISTS (SELECT 1 FROM tablename WHERE event = 1 AND date(ts) = date(t.ts) AND ts < t.ts)
UNION ALL
SELECT date(t.ts) || ' 23:59:59.999999', 0
FROM tablename t
WHERE event = 1 AND NOT EXISTS (SELECT 1 FROM tablename WHERE event = 0 AND date(ts) = date(t.ts) AND ts > t.ts)
)
SELECT date(ts) date,
SUM(strftime('%s', ts) - strftime('%s', prev_ts)) total
FROM (
SELECT *, LAG(ts) OVER (ORDER BY ts) prev_ts
FROM cte
)
WHERE event = 0
GROUP BY date
You will get the total per day in seconds.
If you want better accuracy you can use the function julianday() instead of strftime():
..............................
SELECT date(ts) date,
SUM(julianday(ts) - julianday(prev_ts)) * 24 * 3600 total
..............................
Or, a more efficient way:
WITH cte AS (
SELECT *,
LAG(ts, 1, date(ts)) OVER (PARTITION BY date(ts) ORDER BY ts) start_ts,
event = 1 AND LEAD(ts) OVER (PARTITION BY date(ts) ORDER BY ts) IS NULL flag
FROM tablename
)
SELECT date(ts) date,
SUM(
CASE flag
WHEN 0 THEN strftime('%s', ts) - strftime('%s', start_ts)
WHEN 1 THEN strftime('%s', date(ts) || ' 23:59:59.999999') - strftime('%s', ts)
END
) total
FROM cte
WHERE event = 0 OR flag = 1
GROUP BY date
Note that this code works only if all datetimes are in the format YYYY-MM-DD hh:mm:ss.ssssss (I noticed that in your sample data there is a value that is not of that format: '2020-12-26 9:00:00.589016').
See the demo.
Results:
> date | total
> :--------- | ----:
> 2020-12-26 | 70544
You can find the difference between the start and stop times for each interval, and then sum the latter result, grouped by the day:
with _events as (select row_number() over (order by t1.id) r, substr(t1.ts, 0, instr(t1.ts, " ")) day, t1.* from test t1),
events as (select (select sum(e2.event = 1 and e2.r < e1.r) from _events e2) c, e1.* from _events e1)
select day_r.day, sum(diff) from (
select e3.day, (julianday(max(e3.ts)) - julianday(min(e3.ts)))*24*60*60 diff
from events e3
group by e3.c
)
day_r group by day_r.day;

TSQL Calendar table, count 10 workings days from date

I have a calendar table which stores rows of dates and an indication of wether that date is a holiday or working day.
How can I select the date that is 5 working days into the future from the 2014-12-22 so the selected date will be 2014-12-31
Date_Id Date_Date Date_JDE Is_WorkingDay
20141222 2014-12-22 114356 1
20141223 2014-12-23 114357 1
20141224 2014-12-24 114358 1
20141225 2014-12-25 114359 0
20141226 2014-12-26 114360 0
20141227 2014-12-27 114361 0
20141228 2014-12-28 114362 0
20141229 2014-12-29 114363 1
20141230 2014-12-30 114364 1
20141231 2014-12-31 114365 1
You can use a CTE like this...
;WITH cteWorkingDays AS
(
SELECT Date_Date, ROW_NUMBER() OVER (ORDER BY Date_Date) as 'rowNum'
FROM TableName
WHERE Is_WorkingDay = 1
and Date_Date > '20141222' -- this will be a param I suppose
)
SELECT Date_Date
FROM cteWorkingDays
WHERE rowNum = 5 -- this can be changed to 10 (title value
This is hand typed, but it will be close enough.
EDIT: Based on comment.
Declare #DateToUse TYPE -- unsure if you're using a string or a date type.
SELECT #DateToUse = Date_Date
FROM cteWorkingDays
WHERE rowNum = 5
...;
WITH DatesCTE AS
(
SELECT Date_Id,
Date_Date,
Date_JDE,
Is_WorkingDay,
ROW_NUMBER() OVER(ORDER BY Date_Date) AS rn
FROM DatesTable
WHERE Is_WorkingDay = 1
AND Date_Date > '2014-12-22'
)
SELECT Date_Date
FROM DatesCTE
WHERE rn = 5
SQL Fiddle Demo
with Derived Tables
select * from
(
SELECT Date_Date, ROW_NUMBER() OVER (ORDER BY Date_Date) as 'RowNum'
FROM Table_calendar
WHERE Is_WorkingDay = 1
and CAST(Date_Date as DATE) > '2014-12-22'
)d
where d.RowNum=5
You can Try Like This:
with calender as
(select top 5 date_id,date_date,date_jde from calender
where date_date>='2014-12-22' and is_workingday='1)calender
select top 1 * from calender order by date_date desc