根据特定 start/end 值过滤数据并计算时间

Filter data based on specific start/end values and calculate time

我在过滤掉特定数据集以捕获子集并计算该子集的持续时间时遇到问题。

我们有 PC 在某些任务开启时进行记录;开机时间、BIOS 加载时间、windows 启动时间、应用程序启动时间等——以及这些任务关闭的时间。我的目标集中在 windows 开启的时间和 on/off 关闭 windows 之前的任务以及 windows 开启 on/off 的持续时间。

原始数据集:

    PCNum   Code        On/Off      Date        Time
1.  5258    power       on          10/11/2019  10:00:00
2.  5258    bios        on          10/11/2019  10:00:10
3.  5258    windows     on          10/11/2019  10:00:20
4.  5258    steam       on          10/11/2019  10:00:55
5.  5258    origin      on          10/11/2019  10:01:03
6.  5258    origin      off         10/11/2019  10:10:04
7.  5258    steam       off         10/11/2019  10:12:45
8.  5258    windows     off         10/11/2019  10:13:06
9.  5258    bios        off         10/11/2019  10:14:01
10. 5258    power       off         10/11/2019  10:14:22
11. 5258    power       on          10/11/2019  11:34:45
12. 5258    bios        on          10/11/2019  11:34:56
13. 5258    windows     on          10/11/2019  11:35:03
14. 5258    skype       on          10/11/2019  11:35:06
15. 5258    skype       off         10/11/2019  11:56:52
16. 5258    windows     off         10/11/2019  11:57:07
17. 5258    bios        off         10/11/2019  11:57:36
18. 5258    power       off         10/11/2019  11:57:48

使用 CTE,我已经能够过滤我的数据,因此它在 windows 开启时进行第一次迭代,在关闭时进行最后一次迭代,如下所示:

with minTime_cte (PCNum, Code, Date, Time)
as
    -- find first occurance of on for windows code
    (select PCNum,
            Code,
            Date,
            Time = min(Time)
    from PCData
    where Code = 'windows' and [On/Off] = 'on'
    group by PCNum, Code),

maxTime_cte (PCNum, Code, Date, Time)
as
    -- find last occurrence of off for windows code
    (select PCNum,
            Code,
            Date,
            Time = max(Time)
    from PCData
    where Code = 'windows' and [On/Off] = 'off'
    group by PCNum, Code),

select PCNum,
       Code,
       [On/Off],
       Date,
       Time
from PCData
  join minTime_cte on minTime_cte.Date = Date
    and minTime_cte.PCNum = PCNum
  join maxTime_cte on maxTime_cte.Date = Date
    and maxTime_cte.PCNum = PCNum
where Time >= minTime_cte.Time
    and Time <= maxTime_cte.Time
order by PCNum, Date, Time
    PCNum   Code        On/Off      Date        Time
1.  5258    windows     on          10/11/2019  10:00:20
2.  5258    steam       on          10/11/2019  10:00:55
3.  5258    origin      on          10/11/2019  10:01:03
4.  5258    origin      off         10/11/2019  10:10:04
5.  5258    steam       off         10/11/2019  10:12:45
6.  5258    windows     off         10/11/2019  10:13:06
7.  5258    bios        off         10/11/2019  10:14:01
8.  5258    power       off         10/11/2019  10:14:22
9.  5258    power       on          10/11/2019  11:34:45
10. 5258    bios        on          10/11/2019  11:34:56
11. 5258    windows     on          10/11/2019  11:35:03
12. 5258    skype       on          10/11/2019  11:35:06
13. 5258    skype       off         10/11/2019  11:56:52
14. 5258    windows     off         10/11/2019  11:57:07

现在我需要进一步过滤集合,以排除 windows 关闭和再次打开之间的任何任务。在这种情况下,它将删除记录 7、8、9 和 10。我还需要计算从 windows 打开到关闭的持续时间。

这是我想要的结果:

    PCNum   Code        On/Off      Date        Time        DurationOfWindowsSec
1.  5258    windows     on          10/11/2019  10:00:20    0:12:46
2.  5258    steam       on          10/11/2019  10:00:55    0:12:46
3.  5258    origin      on          10/11/2019  10:01:03    0:12:46
4.  5258    origin      off         10/11/2019  10:10:04    0:12:46
5.  5258    steam       off         10/11/2019  10:12:45    0:12:46
6.  5258    windows     off         10/11/2019  10:13:06    0:12:46
7.  5258    windows     on          10/11/2019  11:35:03    0:22:04
8.  5258    skype       on          10/11/2019  11:35:06    0:22:04
9.  5258    skype       off         10/11/2019  11:56:52    0:22:04
10. 5258    windows     off         10/11/2019  11:57:07    0:22:04

任何 help/direction 将不胜感激。谢谢!

我解决这个问题的方法是弄清楚最后一个 windows 事件是什么,然后根据它进行过滤。如果最后一个事件是on,那么我们应该记录它;否则我们不会。作为奖励,这意味着您可能不需要之前的预处理——我还没有测试所有的边缘情况,但是如果您的数据以 windows off 开头,那么您应该很好(这样您就可以合并有一个虚拟行)

请注意,为方便起见,我对数据类型做了一些小改动——我将日期和时间列转换为单个日期时间列,并将 OnOff 转换为位列。回过头来看,也许第二次调整是没有必要的。进行第一次调整应该很容易;您可以将其作为预处理 CTE 的一部分。

我已经把你的数据放到了一个临时文件中 table,所以我可以更容易地进行实验;您可能希望将 #test 的每个实例替换为您的 CTE 的名称。

SQLFiddle/数据定义:

create table #test(
    id int primary key identity,
    PCNum int,
    Code varchar(50),
    OnOff bit,
    dt datetime
)
;

insert into #test(PCNum, Code, OnOff, dt)
values
(5258, 'windows'  , 1,  '10/11/2019 10:00:20'),
(5258, 'steam'    , 1,  '10/11/2019 10:00:55'),
(5258, 'origin'   , 1,  '10/11/2019 10:01:03'),
(5258, 'origin'   , 0,  '10/11/2019 10:10:04'),
(5258, 'steam'    , 0,  '10/11/2019 10:12:45'),
(5258, 'windows'  , 0,  '10/11/2019 10:13:06'),
(5258, 'bios'     , 0,  '10/11/2019 10:14:01'),
(5258, 'power'    , 0,  '10/11/2019 10:14:22'),
(5258, 'power'    , 1,  '10/11/2019 11:34:45'),
(5258, 'bios'     , 1,  '10/11/2019 11:34:56'),
(5258, 'windows'  , 1,  '10/11/2019 11:35:03'),
(5258, 'skype'    , 1,  '10/11/2019 11:35:06'),
(5258, 'skype'    , 0,  '10/11/2019 11:56:52'),
(5258, 'windows'  , 0,  '10/11/2019 11:57:07')
;

实际查询:

with last_windows(id, dt) as 
(
    select t1.id, max(t2.dt) as dt from #test as t1
    cross join #test as t2
    where datediff(second, t1.dt, t2.dt) <= 0
        and t2.code = 'windows'
    group by t1.id
),
windows_active(id, OnOff) as 
(
select w.id, t.OnOff
from #test as t
    left join last_windows as w on w.dt = t.dt
where t.Code = 'windows'
)
select t.id, t.PCNum, t.Code, t.OnOff, t.dt 
from #test as t
    left join windows_active as wa on wa.id = t.id
where wa.OnOff = 1
    or t.Code = 'windows'
;

说明:CTE last_windows 负责识别每一行(ID)最后windows activity 的时间。它通过交叉连接 table 来实现这一点。第一个实例命名为 t1,第二个实例命名为 t2t2 被过滤为仅包含 windows 行,并且还仅包含 t1 或之前的事件。如果我们然后为 t1 中的每个 id 取最大 t2 日期时间,这表示最后 windows activity.

的时间

由于聚合的工作方式,我们实际上无法将 OnOff 数据添加到 last_windowswindows_active 提供了一个便利 - 它将 #test 连接到 last_windows,有效地将 OnOff 数据添加到 last_windows。我们剩下的是一个 table,它告诉我们最后一个 windows activity 是什么,对于 #test.

中的每一行

查询的其余部分很简单 - 查询将 windows_active 连接到 #test 并仅在最后一个 windows 事件为 'on' 时进行过滤。这也会过滤掉任何 windows 被关闭的情况,因此有一个 or 语句来包含所有 windows 事件。

请注意,不幸的是,由于交叉连接,此查询在大型数据集上可能会很慢。