联合重叠时间范围,然后总计总时间

时间:2015-05-12 16:43:31

标签: sql sql-server

我有一个硬件组和许多设备进入该组。 例如:

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  |

4 个答案:

答案 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