给定多个开始时间时如何创建开始和停止时间?

How to create start & stop times when multiple start times are given?

我正在尝试量化活动时间与空闲时间,我需要做的第一件事是创建不同且离散的开始时间和结束时间。问题是数据库(有人告诉我这是一个错误)为事件创建了多个 "start" 次。更复杂的是,一个 "report" 可以有多个正在处理的实例,并且每个实例都应该记录为一个离散的持续时间。

例如,

WorkflowID ReportID User Action Timestamp      
1           1         A  Start   1:00      
2           1         A  Stop    1:03      
3           1         B  Start   1:05      
4           1         B  Start   1:06      
5           1         B  Stop    1:08      
6           1         B  Start   1:10      
7           1         B  Start   1:11      
8           1         B  Stop    1:14

我想编写一个 SQL 查询来输出以下内容:

User StartTime EndTime
A     1:00      1:03
B     1:05      1:08
B     1:10      1:14

我 运行 遇到的问题是 start/stop 事件的数量需要是任意的(每个用户每个 ReportID)。此外,需要删除系列中第一个 "start" 和随后的 "stop" 之间多余的 "start" 次,以免弄乱。

也许我遗漏了什么,但这对我来说很棘手。有什么想法吗?谢谢。

要删除重复数据,请使用 lag() 将用户之前的操作和报告与当前操作进行比较。如果它们相同,则为重复项,将其标记为重复项。然后使用 row_number() 对开始和停止进行编号,以便每对属于一起的开始和停止共享一个数字(每个报告和用户)。然后加入报告,用户和那个号码。

为方便起见,您可以使用 CTE 来构造查询并避免重复某些子查询的必要性。

WITH
[DeduplicatedAndNumbered]
AS
(
SELECT [WorkflowID],
       [ReportID],
       [User],
       [Action],
       [Timestamp],
       row_number() OVER (PARTITION BY [ReportID],
                                       [User],
                                       [Action]
                          ORDER BY [Timestamp]) [Number]
       FROM (SELECT [WorkflowID],
                    [ReportID],
                    [User],
                    [Action],
                    [Timestamp],
                    CASE
                      WHEN lag([Action]) OVER (PARTITION BY [ReportId],
                                                            [User]
                                               ORDER BY [Timestamp]) = [Action] THEN
                        1
                      ELSE
                        0
                   END [IsDuplicate]
                   FROM [elbaT]) [x]
       WHERE [IsDuplicate] = 0
),
[DeduplicatedAndNumberedStart]
AS
(SELECT [WorkflowID],
        [ReportID],
        [User],
        [Action],
        [Timestamp],
        [Number]
        FROM [DeduplicatedAndNumbered]
        WHERE [Action] = 'Start'),
[DeduplicatedAndNumberedStop]
AS
(SELECT [WorkflowID],
        [ReportID],
        [User],
        [Action],
        [Timestamp],
        [Number]
        FROM [DeduplicatedAndNumbered]
        WHERE [Action] = 'Stop')
SELECT [DeduplicatedAndNumberedStart].[User],
       [DeduplicatedAndNumberedStart].[Timestamp] [StartTime],
       [DeduplicatedAndNumberedStop].[Timestamp] [EndTime]
       FROM [DeduplicatedAndNumberedStart]
            INNER JOIN [DeduplicatedAndNumberedStop]
                       ON [DeduplicatedAndNumberedStart].[ReportId] = [DeduplicatedAndNumberedStop].[ReportId]
                          AND [DeduplicatedAndNumberedStart].[User] = [DeduplicatedAndNumberedStop].[User]
                          AND [DeduplicatedAndNumberedStart].[Number] = [DeduplicatedAndNumberedStop].[Number];

db<>fiddle

OP 用 sql-server-2008 标记了他们的问题。

由于 SQL Server 2008 缺少 lag() 功能(它是在 SQL Server 2012), here is a solution that uses Common Table Expressions and row_number() 中添加的,可从 SQL Server 2005 起使用...

;with [StopEvents] as (
    select  [WorkflowID],
        [ReportID],
        [User],
        [EndTime] = [Timestamp],
        [StopEventSeq] = row_number() over (
            partition by [ReportID], [User], [Timestamp]
            order by [Timestamp])
    from Workflow
    where [Action] = 'Stop'
)
select  this.[User], [StartTime], this.[EndTime]
from [StopEvents] this
-- Left join here because first Stop event won't have a previous Stop event
left join [StopEvents] previous
    on previous.[ReportID] = this.[ReportID]
    and previous.[User] = this.[User]
    and previous.[StopEventSeq] = this.[StopEventSeq] - 1
outer apply (
    select  [StartTime] = min([Timestamp])
    from    Workflow W
    where   W.[ReportID] = this.[ReportID]
    and W.[User] = this.[User]
    and W.[Timestamp] < this.[EndTime]
    -- First Stop event won't have a previous, so just get the min([Timestamp])
    and (previous.[EndTime] is null or W.[Timestamp] >= previous.[EndTime])
) thisStart
order by this.[User], this.[EndTime]