【问题标题】:SQL Server - Duration between similar events for each VisitSQL Server - 每次访问的类似事件之间的持续时间
【发布时间】:2014-07-01 05:58:09
【问题描述】:

我正在尝试计算信息亭机器上事件之间的时间差(以秒为单位)。每次有人开始使用屏幕时,它都会创建一个 VisitID。在那次访问中,创建了一系列计算机事件,这些事件决定了屏幕的开始和屏幕的结束我已经确定了每个屏幕的每个事件。根据用户做出的选择,他们可能会或可能不会经历每个事件或屏幕。我的数据如下所示:

VisitID       ScreenTime           EventName
1              13:24:08         WelcomeScreen_Begin
1              13:24:15         Welcome_End
1              13:24:15         NameScreenInit_Start
1              13:24:32         Name_Screen_Terminate
1              13:24:33         CompanyScreen_Enter
1              13:24:45         Company_Lookup
1              13:24:45         Signoff_Now
1              13:24:52         SignOutEnd
2              13:45:02         WelcomeScreen_Begin
2              13:45:15         Welcome_End
2              13:45:15         NameScreenInit_Start
2              13:45:40         Name_Screen_Terminate
2              13:45:40         Signoff_Now
2              13:45:58         SignOutEnd

我希望我的结果如下所示:

VisitID       WelcomeScreen       NameScreen      CompanyScreen    SignoffScreen
1               00:00:7           00:00:17           00:00:12          00:00:07
2               00:00:13          00:00:25              NULL           00:00:18

最后,我想计算在每个屏幕上花费的平均时间:

WelcomeScreenAVG      NameScreenAVG      CompanyScreenAVG      SignoffScreenAVG
00:00:10              00:00:21           00:00:12              00:00:13

我已经能够使用下标来获取 DATEDIFF 子句中的每个时间戳,但只有在 WHERE 子句中包含 VisitID 时它才有效。我有成千上万的条目,所以这不是这样做的方法。这几天我一直在绞尽脑汁。有没有人有解决方案,或者至少知道我应该做什么?提前致谢!

在少数情况下,唯一可用于表示一个屏幕开始的事件与结束前一个屏幕的事件相同。例如:

VisitID       ScreenTime       EventName
34            08:34:36         Delivery_summaryConfirm
34            08:34:47         Seal_questionsBegin
34            08:35:17         EndSeal_Inquiry

^^这是 2 个屏幕^^ 在上面的示例中,虽然 Seal_questionsBegin 是 SealInquiry 屏幕的开始,但它是唯一能够表示 DeliverySummary 屏幕结束的事件。这是因为 DeliverySummary 屏幕后面总是跟着一个 SealInquiry 屏幕。

【问题讨论】:

  • 事件是稳定的少数还是一长串变量字符串?在第二种情况下,您可能需要一个动态枢轴

标签: sql-server


【解决方案1】:

您首先需要的是规范化事件名称,查询会处理这个问题,如果可以的话,您应该规范化数据或至少在您的数据库中创建一个翻译表

编辑
这些事件比我最初想象的要多,翻译应该创建为一个完整的表格。

我们可以利用Translate 表,添加 IsBegin 和 IsEnd 位字段,并将 NormName 字段映射到屏幕名称,除了字典之外,还可以简化 Times CTE 中的逻辑。

CREATE TABLE NormalizeEvents (
  RealName VARCHAR(50)
, NormName VARCHAR(50)
, IsBegin BIT
, IsEnd BIT
)

INSERT INTO NormalizeEvents (RealName, NormName, IsBegin, IsEnd)
VALUES ('WelcomeScreen_Begin', 'WelcomeScreen', 1, 0)
     , ('Welcome_End', 'WelcomeScreen', 0, 1)
     , ('NameScreenInit_Start', 'NameScreen', 1, 0)
     , ('Name_Screen_Terminate', 'NameScreen', 0, 1)
     , ('CompanyScreen_Enter', 'CompanyScreen', 1, 0)
     , ('Company_Lookup', 'CompanyScreen', 0, 1)
     , ('Signoff_Now', 'SignoffScreen', 1, 0)
     , ('SignOutEnd', 'SignoffScreen', 0, 1)
     , ('Delivery_summaryConfirm', 'DeliverySummary', 1, 0)
     , ('Seal_questionsBegin', 'DeliverySummary', 0, 1)
     , ('Seal_questionsBegin', 'SealQuestions', 1, 0)
     , ('EndSeal_Inquiry', 'SealQuestions', 0, 1)

如果事件名称既是事件的结束又是新事件的开始,则应插入两次,如示例中的“Seal_questionsBegin”

WITH Times AS (
  SELECT VisitID, EventName = t.NormName
       , ScreenTimeBegin = Max(CASE t.IsBegin
                                    WHEN 1 THEN ScreenTime
                                    ELSE NULL
                               END)
       , ScreenTimeEnd = Max(CASE t.IsEnd
                                  WHEN 1 THEN ScreenTime
                                  ELSE NULL
                             END)
  FROM   Events e
         INNER JOIN NormalizeEvents t ON e.EventName = t.RealName
  GROUP BY VisitID, NormName
)
SELECT VisitID
     , [WelcomeScreen] = CAST(DATEADD(ss, WelcomeScreen, '1900-01-01') as Time)
     , [CompanyScreen] = CAST(DATEADD(ss, CompanyScreen, '1900-01-01') as Time)
     , [NameScreen] = CAST(DATEADD(ss, NameScreen, '1900-01-01') as Time)
     , [SignoffScreen] = CAST(DATEADD(ss, SignoffScreen, '1900-01-01') as Time)
     , [DeliverySummary]=CAST(DATEADD(ss, DeliverySummary, '1900-01-01') as Time)
     , [SealQuestions] = CAST(DATEADD(ss, SealQuestions, '1900-01-01') as Time)
FROM   (SELECT VisitID, EventName
             , Duration = DateDiff(ss, ScreenTimeBegin, ScreenTimeEnd)
        FROM   Times) SOURCE
       PIVOT (AVG(Duration) FOR EventName IN
              ([WelcomeScreen], [CompanyScreen], [NameScreen], [SignoffScreen]
             , [DeliverySummary], [SealQuestions])
             ) pvt

SQLFiddle demo.

要获得每个屏幕的全局平均值,只需删除 PIVOT 和主查询的源中的 VisitID 字段。

WITH Times AS (
  SELECT VisitID, EventName = t.NormName
       , ScreenTimeBegin = Max(CASE t.IsBegin
                                    WHEN 1 THEN ScreenTime
                                    ELSE NULL
                               END)
       , ScreenTimeEnd = Max(CASE t.IsEnd
                                  WHEN 1 THEN ScreenTime
                                  ELSE NULL
                             END)
  FROM   Events e
         INNER JOIN NormalizeEvents t ON e.EventName = t.RealName
  GROUP BY VisitID, NormName
)
SELECT [WelcomeScreen] = CAST(DATEADD(ss, WelcomeScreen, '1900-01-01') as Time)
     , [CompanyScreen] = CAST(DATEADD(ss, CompanyScreen, '1900-01-01') as Time)
     , [NameScreen] = CAST(DATEADD(ss, NameScreen, '1900-01-01') as Time)
     , [SignoffScreen] = CAST(DATEADD(ss, SignoffScreen, '1900-01-01') as Time)
     , [DeliverySummary]=CAST(DATEADD(ss, DeliverySummary, '1900-01-01') as Time)
     , [SealQuestions] = CAST(DATEADD(ss, SealQuestions, '1900-01-01') as Time)
FROM   (SELECT EventName
             , Duration = DateDiff(ss, ScreenTimeBegin, ScreenTimeEnd)
        FROM   Times) SOURCE
       PIVOT (AVG(Duration) FOR EventName IN
              ([WelcomeScreen], [CompanyScreen], [NameScreen], [SignoffScreen]
             , [DeliverySummary], [SealQuestions])
             ) pvt

SQLFiddle demo(数据过期)。

由于问题中未指定 SQLServer 的版本,我假设是 SQLServer 2012 或更高版本。

【讨论】:

  • 这看起来可行。肯定有时间使用屏幕超过 59 秒。我可以以某种方式使用 DATEDIFF 吗?
  • 当一个屏幕的开头与前一个屏幕的结尾共享相同的事件时,这仍然有效吗?这种情况在我的数据中确实发生过几次。
  • 我已经编辑了原始问题以详细说明情况。
  • 我在 DATEADD 语句中收到一条错误消息:WelcomeScreen、CompanyScreen、NameScreen、SignoffScreen、DeliverySummary、SealQuestions 是 MSG 207 无效的列名。
  • 非常感谢!它现在正在返回结果。它查找每次访问的每个屏幕的持续时间。所以它返回 5559 行和每行的屏幕持续时间。我将如何更改它来为每个屏幕提供一个全局 AVG 时间?
【解决方案2】:

将您的数据集拆分为“开始”和“结束”,然后进行简单的连接和分组:

SELECT 
    Begins.ScreenName,
    AVG(DateDiff(second, Begins.ScreenTime, Ends.ScreenTime)) AS TimeSpentSeconds
FROM (
    SELECT 
        VisitID, 
        LEFT(EventName, LEN(EventName)-5) AS ScreenName, 
        ScreenTime 
    FROM MyTable WHERE EventName LIKE '%Begin'
) AS Begins
INNER JOIN (
    SELECT 
        VisitID, 
        LEFT(EventName, LEN(EventName)-3) AS ScreenName, 
        ScreenTime 
    FROM MyTable WHERE EventName LIKE '%End'
) AS Ends
ON Begins.VisitID = Ends.VisitID AND Begins.ScreenName = Ends.ScreenName
GROUP BY Begins.ScreenName

这以逐行方式返回数据:

ScreenName      TimeSpentSeconds
----------      ----------------
WelcomeScreen   10
NameScreen      21
CompanyScreen   12
SignoffScreen   13

使用PIVOT 将其更改为按列布局。要将秒数更改为小时-分钟-秒,请参阅例如 this post

【讨论】:

  • 得到一个不明确的列名“屏幕名称”错误,这也不是作为 OP 请求返回数据
  • 我的错。将SELECT ScreenName ... 更改为SELECT Begins.ScreenName ...,同样在查询的GROUP BY 部分。我已经更新了我的答案。
  • 非常感谢!在我的数据中,它与我给出的示例并不完全相同。 并不像 EventBegin 和 EventEnd 那样简单。它们实际上是非常随机的。我很抱歉让它看起来像那样。我应该怎么做而不是使用 LEN(EventName)-5 和 LEN(EventName)-3?例如,我的事件的一些名称是“PG_IG_DriverID_HostInquiry”和“PG_IG_DriverID_HostDriverInfo”。这些事件是一个屏幕的开始和结束。

    **我已经确定了构成每个屏幕的两个事件**

  • 上面的查询只过滤那些名称以“End”或“Begin”结尾的事件。您需要考虑其他名称吗?在这种情况下,我认为您最好的选择是创建一个临时表,该表指定应如何对事件名称进行分组,例如,该表可以保存一条记录为“TestEventABCStart”,另一条记录为“TestEventXYZStop”。然后,两者都会有一个值为“TestEvent”的列用于分组。这样,您可以加入这个临时表,然后在上面的查询中使用分组列名。
【解决方案3】:

更新

如果您有每个屏幕的固定事件列表,您可以执行以下操作。我认为它看起来更容易阅读并且比其他帖子更简单......但那就是我。

New Sql Fiddle

With tblUserTimes as (
  Select VisitId, 

  DateDiff(second, 
  Max(Case When EventName = 'WelcomeScreen_Begin' Then ScreenTime End),
  Max(Case When EventName = 'Welcome_End' Then ScreenTime End)) as WelcomeScreen,

  DateDiff(second, 
  Max(Case When EventName = 'NameScreenInit_Start' Then ScreenTime End),
  Max(Case When EventName = 'Name_Screen_Terminate' Then ScreenTime End)) as NameScreen,

  DateDiff(second, 
  Max(Case When EventName = 'CompanyScreen_Enter' Then ScreenTime End),
  Max(Case When EventName = 'Company_Lookup' Then ScreenTime End)) as CompanyScreen,

  DateDiff(second, 
  Max(Case When EventName = 'Signoff_Now' Then ScreenTime End),
  Max(Case When EventName = 'SignOutEnd' Then ScreenTime End)) as SignoffScreen

  from Visits
  Group By VisitId)

Select AVG(WelcomeScreen) as WelcomeScreenAvg,
       AVG(NameScreen) as NameScreenAvg,
       AVG(CompanyScreen) as CompanyScreenAvg,
       AVG(SignoffScreen) as SignoffScreenAvg
from tblUserTimes

参见this SQL Fiddle,例如枢轴。

【讨论】:

  • 我已经编辑了我的原始问题以更准确地表示我的数据。事件的名称并不简单。它们是相当随机的。例如,我的事件的一些名称是“PG_IG_DriverID_HostInquiry”和“PG_IG_DriverID_HostDriverInfo”。这些事件是一个屏幕的开始和结束。我已经确定了每个屏幕的所有事件。
猜你喜欢
  • 2020-05-05
  • 2017-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-16
相关资源
最近更新 更多