I have a single table of on/off events for different assets. I need to get a list of start & stop event times without using a cursor.
Source:
Item
declare @YourTable table (Item varchar(10), EventDate datetime, Event varchar(10))
insert into @YourTable values
('A','2011-10-03 00:01:00''On'),
('B','2011-10-03 00:01:00','On'),
('A','2011-10-03 00:02:00','Off'),
('C','2011-10-03 00:01:00','On'),
('B','2011-10-03 00:02:00','Off'),
('A','2011-10-03 00:02:02','On'),
('C','2011-10-03 00:02:05','Off'),
('A','2011-10-03 00:02:07','Off'),
('D','2011-10-03 00:02:02','On')
;with a as
(
select item, eventdate, event, ROW_NUMBER() Over(Partition by Item order by EventDate) RN
from @YourTable
),
b as (
select item, eventdate, 0 e, rn, event from a where rn = 1
union all
select b.item, a.eventdate, case when b.event = 'Off' then b.e+1 else b.e end, a.rn, a.event
from b
join a on b.rn + 1 = a.rn and a.item = b.item
)
select item, min(eventdate) Start, max(eventdate) [End]
from b
group by item, e
order by item, e
Untested...
;WITH myCTE AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Item ORDER BY EventDate) AS rn
FROM MyTable
)
SELECT
M1.Item,
M1.EventDate AS Start,
M2.EventDate AS End
FROM
myCTE M1
JOIN
myCTE M2 ON M1.Item = M2.Item AND M1.rn+1 = M2.rn
WHERE
M1.Event = 'On'
AND
M2.Event = 'Off';
Here's the solution, with proof of concept for everyone to verify.
I noticed that the Off
event for C
was marked as a new On
. I fixed that, but that also led me to coding a solution that would allow having an event that had started but not finished, so I included an open ended event D
.
Additionally, my solution works with overlapping periods.
declare @YourTable table (Item varchar(10),
EventDate datetime,
Event varchar(10))
insert into @YourTable values
('A', '2011-10-03 00:01:00', 'On'),
('B', '2011-10-03 00:01:00', 'On'),
('A', '2011-10-03 00:02:00', 'Off'),
('C', '2011-10-03 00:01:00', 'On'),
('B', '2011-10-03 00:02:00', 'Off'),
('A', '2011-10-03 00:02:02', 'On'),
('C', '2011-10-03 00:02:05', 'Off'),
('A', '2011-10-03 00:02:07', 'Off'),
('D', '2011-10-03 00:02:02', 'On')
select tOn.Item, tOn.EventDate Start, tOff.EventDate [End]
from (
select Item, EventDate,
ROW_NUMBER() Over(Partition by Item order by EventDate) EventID
from @YourTable where Event = 'On'
) tOn
LEFT JOIN (
select Item, EventDate,
ROW_NUMBER() Over(Partition by Item order by EventDate) EventID
from @YourTable where Event = 'Off'
) tOff
on (tOn.Item = tOff.Item and tOn.EventID = tOff.EventID)
Explained
We divide the data set in 2: On
events and Off
events. Each containing a numbered row which restarts when Item
changes.
Basically, we have a first in first out: The first On
will be closed by the first Off
, so overlapping periods will be supported by this query given this approach. So each On
Event for A
will have its Event ID
, which will be linked to the correspondent Off
Event ID
.
Open ended periods will be supported by the LEFT JOIN
.