我有一个硬件组和许多设备进入该组。 例如:
io.to('battleRoom').emit('receiveMessage', 'SYSTEM: Battle begun');
使用ping监控所有设备。当某些设备不工作时,程序会在表中添加一行,说明中断的开始。当设备重新启动时,程序会更新此行,说明休息结束。
可以知道每个设备的总休息秒数。 我需要知道所有小组的实际总和时间。例如:
+ Room 1
|-- Computer
|-- Camera
+ Room 2
|-- Computer
|-- Switch
群组“会议室1”的真实停机时间为39秒(不是58)。
Group Device Start End
Room 1 Computer 2015-05-12 01:40:00 2015-05-12 01:40:20
Room 1 Camera 2015-05-12 01:40:01 2015-05-12 01:40:27
Room 2 Computer 2015-05-12 03:43:03 2015-05-12 03:46:14
Room 2 Switch 2015-05-12 03:43:00 2015-05-12 03:46:12
Room 1 Camera 2015-05-12 07:12:10 2015-05-12 07:12:22
关于两个第一行,看看为什么是27秒而不是46秒:
01:40:00 - 01:40:20 = 20 seconds
01:40:01 - 01:40:27 = 7 seconds
07:12:10 - 07:12:22 = 12 seconds
嗯......我有很多小组,每组都有很多设备。 我怎么能用SQL做到这一点?
帮助测试...
| 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 |
| 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 |
答案 0 :(得分:2)
您希望将不同的群体组合到" islands"并计算岛屿的延伸。这就是为什么这类问题有时被称为差距和岛屿。
我假设您使用的是SQL Server 2012+。这略微简化了计算。我们的想法是确定重叠组的开始和结束。以下确定组是否有重叠:
select t.*,
(case when exists (select 1
from @tblstatus t2
where t2.group_id = t.group_id and
t2.dtend > t.dtstart and t2.dtstart <= t.dtstart and
t2.id < t.id
)
then 0 else 1 end) as NoOverlapBefore
from @tblstatus t
有了这个,你可以只为表格中的每一行分配&#34; NoOverlapBefore&#34;发生在它之前的记录并使用结果进行聚合:
with t as (
select t.*,
(case when exists (select 1
from @tblstatus t2
where t2.group_id = t.group_id and
t2.dtend > t.dtstart and t2.dtstart <= t.dtstart and
t2.id < t.id
)
then 0 else 1 end) as NoOverlapBefore
from @tblstatus t
)
select group_id,
datediff(second, min(dtstart), max(dtend)) as total_seconds
from (select t.*,
sum(NoOverlapBefore) over (partition by group_id order by dtstart, id) as grp
from @tblstatus t
) t
group by group_id;
编辑:
我误解了有关您的数据结构的一些事情。 SQL Fiddle是一个很大的帮助。 Here实际上是有效的。
查询是:
WITH t AS (
SELECT t.*, d.group_id,
(CASE WHEN EXISTS (SELECT 1
FROM tblstatus t2 JOIN
tbldevice d2
ON d2.id = t2.device_id
WHERE d2.group_id = d.group_id AND
t2.dtend > t.dtstart AND
t2.dtstart <= t.dtstart AND
t2.id <> t.id
)
THEN 0 ELSE 1
END ) AS NoOverlapBefore
FROM tblstatus t JOIN
tblDevice d
ON t.device_id = d.id
)
SELECT group_id, SUM(total_seconds) as total_seconds
FROM (SELECT group_id, grp,
DATEDIFF(SECOND, MIN(dtstart), MAX(dtend)) AS total_seconds
FROM (SELECT t.*,
sum(t.NoOverlapBefore) over (partition BY group_id
ORDER BY t.dtstart, t.id) AS grp
FROM t
) t
GROUP BY grp, group_id
) t
GROUP BY group_id;
答案 1 :(得分:1)
有点令人费解,但我有一个有效的解决方案。 诀窍是改变数据表示。
编辑:只要同一设备上不会发生两个同时发生的事件,此解决方案就可以正常工作。
我在这里留下了一个SQL小提琴:http://sqlfiddle.com/#!6/59e80/8/0
declare @tblGroup table (id int, name varchar(20))
insert into @tblGroup (id, name) values (1, 'Room 1'), (2, 'Room 2'), (3, 'Room 3'), (4, 'Room 4')
declare @tblDevice table (id int, name varchar(20), group_id int)
insert into @tblDevice (id, name, group_id) values (1, 'Computer', 1), (2, 'Camera', 1), (3, 'Computer', 2), (4, 'Switch', 2)
declare @tblStatus table (id int, device_id int, dtStart datetime, dtEnd datetime)
insert into @tblStatus (id, device_id, dtStart, dtEnd) values
(1, 1, '2015-05-12 01:40:00.0', '2015-05-12 01:40:20.0'),
(2, 2, '2015-05-12 01:40:01.0', '2015-05-12 01:40:27.0'),
(3, 3, '2015-05-12 03:43:03.0', '2015-05-12 03:46:14.0'),
(4, 4, '2015-05-12 03:43:00.0', '2015-05-12 03:46:12.0'),
(5, 2, '2015-05-12 07:12:10.0', '2015-05-12 07:12:22.0');
WITH eventlist as
(select
s.id,
s.device_id,
g.Id AS groupId,
g.name as groupName,
d.name as deviceName,
s.dtStart AS dt,
'GO_DOWN' AS eventtype,
1 AS eventcount
from
@tblStatus s
inner join
@tblDevice d on d.id = s.device_id
inner join
@tblGroup g on g.id = d.group_id
UNION
select
s.id,
s.device_id,
g.Id AS groupId,
g.name as groupName,
d.name as deviceName,
s.dtEND AS dt,
'BACK_UP' AS eventtype,
-1 AS eventcount
from
@tblStatus s
inner join
@tblDevice d on d.id = s.device_id
inner join
@tblGroup g on g.id = d.group_id
),
breakdown AS(
SELECT
principal.groupId
,principal.groupName
,principal.dt
,principal.deviceName
,principal.eventtype
,was_broken = ISNULL(SUM(before.eventcount),0)
,is_broken = ISNULL(SUM(before.eventcount),0) + principal.eventcount
FROM
eventlist principal
LEFT JOIN eventlist before ON before.groupId = principal.groupId
AND 1 = CASE WHEN before.dt < principal.dt THEN 1
WHEN before.dt = principal.dt AND before.device_id < principal.device_id THEN 1
ELSE 0 END
GROUP BY
principal.eventcount
,principal.deviceName
,principal.eventtype
,principal.groupId
,principal.groupName
,principal.dt
)
,breakdownstart AS
( SELECT groupId,dt, r = RANK() OVER (PARTITION BY groupId ORDER BY dt) FROM breakdown WHERE was_broken = 0 AND is_broken =1 )
,breakdownend AS
( SELECT groupId,dt, r = RANK() OVER (PARTITION BY groupId ORDER BY dt) FROM breakdown WHERE was_broken = 1 AND is_broken = 0 )
,breakgroup as
(SELECT s.groupId
,s.r
, break_start = s.dt
, break_end = e.dt FROM breakdownstart s INNER JOIN breakdownend e ON e.r = s.r AND e.groupId = s.groupId)
SELECT groupId,SUM(DATEDIFF(SECOND,break_start,break_end)) AS break_length FROM breakgroup GROUP BY breakgroup.groupId
答案 2 :(得分:0)
试试这个:
select
g.id, SUM(DATEDIFF(SECOND, s.dtStart, s.dtEnd))
from
@tblStatus s
inner join @tblDevice d on d.id = s.device_id
inner join @tblGroup g on g.id = d.group_id
group by
g.id
您按GroupId进行分组,然后对于您在该组中的每个状态,您将获得开始时间和结束时间之间的差异,并将SUM汇总到GroupId级别。
答案 3 :(得分:0)
我建议按ID进行分组,这里的目标是获取时间之间的差异,然后才能进行SUM。
SELECT
group.id, SUM(DATEDIFF(SECOND, status.dtStart, status.dtEnd))
FROM
@tblStatus status
inner join @tblDevice device ON device.id = status.device_id
inner join @tblGroup group ON group.id = device.group_id
GROUP BY
group.id