【问题标题】:Query to calculate average time between successive events查询以计算连续事件之间的平均时间
【发布时间】:2009-12-22 15:03:38
【问题描述】:

我的问题是关于如何编写 SQL 查询来计算连续事件之间的平均时间。

我有一张小桌子:

event Name    |    Time

stage 1       |    10:01
stage 2       |    10:03
stage 3       |    10:06
stage 1       |    10:10
stage 2       |    10:15
stage 3       |    10:21
stage 1       |    10:22
stage 2       |    10:23
stage 3       |    10:29

我想构建一个查询,以获取 stage(i) 和 stage(i+1) 之间时间的平均值作为答案。

例如, 第 2 阶段和第 3 阶段之间的平均时间为 5:

(3+6+6)/3 =  5

【问题讨论】:

  • 在 LINQ 中做起来容易得多 ...
  • 您使用的 SQL 方言是什么?

标签: sql average


【解决方案1】:

Aaaaand 带有一点黑魔法:

select a.eventName, b.eventName, AVG(DATEDIFF(MINUTE, a.[Time], b.[Time])) as Average from
     (select *, row_number() over (order by [time]) rn from events) a
join (select *, row_number() over (order by [time]) rn from events) b on (a.rn=b.rn-1)
group by
a.eventName, b.eventName

这将为您提供如下行:

stage3  stage1  2
stage1  stage2  2
stage2  stage3  5

第一列是开始事件,第二列是结束事件。如果在事件 1 之后有事件 3,也会列出。否则,您应该提供一些关于哪个阶段之后是哪个阶段的标准,因此时间仅在这些之间计算。

已添加: 这应该在 Transact-SQL(MSSQL、Sybase)和 PL/SQL(Oracle、PostgreSQL)上都可以正常工作。但是我还没有测试它,仍然可能存在语法错误。这不适用于任何版本的 MySQL。

【讨论】:

  • 其实这个查询也会给你stage 3 stage 1 150 。从要求中不清楚是否需要这样做。我以为不是。
  • 感谢您对我的解决方案发表评论,然后将其窃取为您自己的解决方案,甚至不赞成我的解决方案...
  • @David:Vilx 的代码与您的不同(例如,您不使用 group by),更好地呈现和更好地解释。
  • @APC:作者没有指定如何区分哪个事件在哪个事件之后,以及什么重新启动“序列”的标准。据我们所知,这是我们所希望的。
  • @David - 在您的解决方案中查看我的第二条评论。
【解决方案2】:
Select Avg(differ) from (
 Select s1.r, s2.r, s2.time - s1.time as differ from (
 Select * From (Select rownum as r, inn.time from table inn order by time) s1
 Join (Select rownum as r, inn.time from table inn order by time) s2
 On mod(s2.r, 3) = 2 and s2.r = s1.r + 1
 Where mod(s1.r, 3) = 1)
);

参数可以随着阶段数的变化而变化。这目前设置为从 3 阶段过程中找到阶段 1 和 2 之间的平均值。

修改几个错别字

【讨论】:

  • 注意 - 这是针对 PL/SQL 方言的。
  • 我在编写自己的解决方案时没有看到您的解决方案。但是,如果这是您想要的投票 - 给您! :)
  • 谢谢。我觉得这样说真的很琐碎,但我已经有一段时间被困在 500 以下,而且我想做几个重新标记。
【解决方案3】:

你的桌子设计有缺陷。你怎么知道哪个 stage1 和哪个 stage2 相配?如果没有办法做到这一点,我认为您的查询是不可能的。

【讨论】:

  • 它是一个序列,按时间排序。
  • HLGEM 有道理。我们必须假设这是一个序列化的过程——也就是说,在第 3 阶段运行时,第 1 阶段永远无法启动。但在现实生活中,大多数进程都是多线程/多用户的,因此我们需要一个额外的标识符来隔离流。
【解决方案4】:

最简单的方法是按时间排序并使用游标 (tsql) 来迭代数据。由于游标是邪恶的,因此建议将按时间排序的数据提取到应用程序代码中并在那里迭代。在 SQL 中可能还有其他方法可以做到这一点,但它们会非常复杂并且依赖于非标准的语言扩展。

【讨论】:

    【解决方案5】:

    您没有说您想要回答哪种 SQL。这可能意味着您需要 SQL Server 中的代码(在 SO 标记用法中,[sql] 通常 = [sql-server])。

    但以防万一您(或某些未来的求职者)正在使用 Oracle,这种查询对于分析函数非常简单,在本例中为 LAG()。看看吧:

    SQL> select stage_range
      2         , avg(time_diff)/60 as average_time_diff_in_min
      3  from
      4      (
      5          select event_name
      6                 , case when event_name = 'stage 2' then  'stage 1 to 2'
      7                      when event_name = 'stage 3' then  'stage 2 to 3'
      8                      else  '!!!' end as stage_range
      9                 , stage_secs - lag(stage_secs)
     10                              over (order by ts, event_name) as time_diff
     11                 from
     12                     ( select event_name
     13                              , ts
     14                              , to_number(to_char(ts, 'sssss')) as stage_secs
     15                       from timings )
     16      )
     17         where event_name in ('stage 2','stage 3')
     18  group by stage_range
     19  /
    
    STAGE_RANGE  AVERAGE_TIME_DIFF_IN_MIN
    ------------ ------------------------
    stage 1 to 2               2.66666667
    stage 2 to 3                        5
    
    SQL>
    

    内部查询中的格式更改是必要的,因为我已将 TIME 列存储为 DATE 数据类型,因此我将其转换为秒以使数学更清晰。另一种解决方案是使用 Day to Second Interval 数据类型。但这个解决方案真的是关于LAG()

    编辑

    在我对这个查询的看法中,我没有明确计算过之前的第 3 阶段和随后的第 1 阶段之间的差异。这是一个需求问题。

    【讨论】:

      【解决方案6】:
      WITH    q AS
              (
              SELECT  'stage 1' AS eventname, CAST('2009-01-01 10:01:00' AS DATETIME) AS eventtime
              UNION ALL
              SELECT  'stage 2' AS eventname, CAST('2009-01-01 10:03:00' AS DATETIME) AS eventtime
              UNION ALL
              SELECT  'stage 3' AS eventname, CAST('2009-01-01 10:06:00' AS DATETIME) AS eventtime
              UNION ALL
              SELECT  'stage 1' AS eventname, CAST('2009-01-01 10:10:00' AS DATETIME) AS eventtime
              UNION ALL
              SELECT  'stage 2' AS eventname, CAST('2009-01-01 10:15:00' AS DATETIME) AS eventtime
              UNION ALL
              SELECT  'stage 3' AS eventname, CAST('2009-01-01 10:21:00' AS DATETIME) AS eventtime
              UNION ALL
              SELECT  'stage 1' AS eventname, CAST('2009-01-01 10:22:00' AS DATETIME) AS eventtime
              UNION ALL
              SELECT  'stage 2' AS eventname, CAST('2009-01-01 10:23:00' AS DATETIME) AS eventtime
              UNION ALL
              SELECT  'stage 3' AS eventname, CAST('2009-01-01 10:29:00' AS DATETIME) AS eventtime
              )
      SELECT  (
              SELECT  AVG(DATEDIFF(minute, '2009-01-01', eventtime))
              FROM    q
              WHERE   eventname = 'stage 3'
              ) - 
              (
              SELECT  AVG(DATEDIFF(minute, '2009-01-01', eventtime))
              FROM    q
              WHERE   eventname = 'stage 2'
              )
      

      这取决于这样一个事实,即您始终拥有完整的阶段组,并且它们始终以相同的顺序进行(即stage 1 然后stage 2 然后stage 3

      【讨论】:

      • 你不能用 avg() 而不是 sum() 来简化这个吗?那么,您不必除以计数。
      【解决方案7】:

      我无法发表评论,但我必须同意 HLGEM。虽然您可以通过提供的数据集来判断,但应该让 OP 意识到,仅依赖一次存在的一组阶段可能过于乐观。

      
      event Name    |    Time
      
      stage 1       |    10:01
      stage 2       |    10:03
      stage 3       |    10:06
      stage 1       |    10:10
      stage 2       |    10:15
      stage 3       |    10:21
      stage 1       |    10:22
      stage 2       |    10:23
      stage 1       |    10:25     --- new stage 1
      stage 2       |    10:28     --- new stage 2
      stage 3       |    10:29
      stage 3       |    10:34     --- new stage 3
      

      我们不知道环境或创建数据的原因。由 OP 决定表是否正确构建。

      Oracle 将使用 Analytics 来处理这个问题。就像 Vilx 的回答一样。

      【讨论】:

        【解决方案8】:

        试试这个

           Select Avg(e.Time - s.Time)
           From Table s
             Join Table e 
                 On e.Time = 
                     (Select Min(Time)
                      From Table
                      Where eventname = s.eventname 
                         And time > s.Time)
                 And Not Exists 
                     (Select * From Table
                      Where eventname = s.eventname 
                         And time < s.Time)
        

        对于代表阶段开始的每条记录,此 sql 将其连接到代表结束的记录,获取结束时间和开始时间之间的差值,并对这些差值进行平均。 Not Exists 确保连接到结束记录的开始记录的中间结果集仅包含作为 s... 的开始记录,并且第一个连接条件确保只有一个结束记录(具有相同名称的记录和之后的下一个时间值开始时间)加入它...

        要在加入之后但在取平均值之前查看中间结果集,请运行以下命令:

           Select s.EventName,
               s.Time Startime, e.Time EndTime, 
               (e.Time - s.Time) Elapsed
           From Table s
             Join Table e 
                 On e.Time = 
                     (Select Min(Time)
                      From Table
                      Where eventname = s.eventname 
                         And time > s.Time)
                 And Not Exists 
                     (Select * From Table
                      Where eventname = s.eventname 
                         And time < s.Time)
        

        【讨论】:

        • 我不明白:“并且不存在”条件有什么用?它似乎排除了除第一个事件之外的所有事件,并导致代码发出单个值的平均值(第一个转换时间)...
        • 不存在是为了确保sql只为每个eventstage开始的每条记录输出一行。它从左连接的左侧过滤掉结束记录 - 表别名为“s” - 因为对于这些记录,没有其他记录具有相同的 EventName 和更早的时间。对于 Ending 记录,还有另一条记录(开始记录),因此 Not exists 将其过滤掉。
        • 我不确定这是否有效...您如何获得阶段 + 1?这似乎得到了阶段之间的间隔......即//阶段1(B) - 阶段1(A)。我认为问题是如何区分各个阶段(即// 阶段 2 - 阶段 1)。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-03
        • 2010-10-28
        • 1970-01-01
        • 1970-01-01
        • 2019-02-17
        相关资源
        最近更新 更多