使用SQL检测异常间隔

时间:2013-01-24 19:41:24

标签: sql sql-server sql-server-2008 temporal-database

我的问题很简单:我有一个包含一系列状态和时间戳的表(为了好奇,这些状态表示警报级别),我想查询此表以获得两种状态之间的持续时间。 / p>

看起来很简单,但是这里有一个棘手的部分:我不能创建查找表,程序,它应该尽可能快,因为这个表是一个拥有超过10亿条记录的小怪物(不开玩笑!)。 ..

架构很简单:

[pk]时间 值

(实际上,还有第二个PK,但这对此无用)

低于现实世界的例子:

Timestamp          Status
2013-1-1 00:00:00    1
2013-1-1 00:00:05    2
2013-1-1 00:00:10    2
2013-1-1 00:00:15    2
2013-1-1 00:00:20    0
2013-1-1 00:00:25    1
2013-1-1 00:00:30    2
2013-1-1 00:00:35    2
2013-1-1 00:00:40    0

仅考虑2级警报的输出应如下所示,应报告2级警报的开始及其结束时(达到0时):

StartTime          EndTime            Interval
2013-1-1 00:00:05  2013-1-1 00:00:20     15
2013-1-1 00:00:30  2013-1-1 00:00:40     10

我一直在尝试各种各样的内部连接,但是所有这些都引导我进行了一次惊人的笛卡尔爆炸。你能帮助我找到一种方法来实现这个目标吗?

谢谢!

4 个答案:

答案 0 :(得分:4)

这必须是我今天看到的更难的问题之一 - 谢谢!我假设你可以使用CTE?如果是这样,尝试这样的事情:

;WITH Filtered
AS
(
    SELECT ROW_NUMBER() OVER (ORDER BY dateField) RN, dateField, Status
    FROM Test    
)
SELECT F1.RN, F3.MinRN,
    F1.dateField StartDate,
    F2.dateField Enddate
FROM Filtered      F1, Filtered F2, (
SELECT F1a.RN, MIN(F3a.RN) as MinRN
FROM Filtered      F1a
   JOIN Filtered F2a ON F1a.RN = F2a.RN+1 AND F1a.Status = 2 AND F2a.Status <> 2
   JOIN Filtered F3a ON F1a.RN < F3a.RN AND F3a.Status <> 2
GROUP BY F1a.RN ) F3 
WHERE F1.RN = F3.RN AND F2.RN = F3.MinRN

Fiddle。我没有添加间隔,但我想你可以从这里处理那部分。

祝你好运。

答案 1 :(得分:0)

终于想出了一个我很满意的版本。我记得另一个问题的答案(不记得是哪一个),其中指出两个(增加的)序列之间的差异始终是一个常数。

WITH Ordered (occurredAt, status, row, grp) 
             as (SELECT occurredAt, status, 
                        ROW_NUMBER() OVER (ORDER BY occurredat), 
                        ROW_NUMBER() OVER (PARTITION BY status 
                                           ORDER BY occurredat)
                 FROM Alert)

SELECT Event.startDate, Ending.occurredAt as endDate,
       DATEDIFF(second, Event.startDate, Ending.occurredAt) as interval

FROM (SELECT MIN(occurredAt) as startDate, MAX(row) as ending
      FROM Ordered
      WHERE status = 2
      GROUP BY row - grp) Event

LEFT JOIN (SELECT occurredAt, row
           FROM Ordered
           WHERE status != 2) Ending
        ON Event.ending + 1 = Ending.row

(工作SQL Fiddle example,还有一些额外的数据行用于工作检查)。

遗憾的是,这并未正确处理作为结束行的二级状态(行为未指定),尽管它 列出它们。

答案 2 :(得分:0)

只是为了有另一种选择。试图对性能进行一些测试,但没有完成。

SELECT
  MIN([main].[Start]) AS [Start],
  [main].[End],
  DATEDIFF(s, MIN([main].[Start]), [main].[End]) AS [Seconds]
FROM
(
  SELECT
    [sub].[Start],
    MIN([sub].[End]) AS [End]
  FROM
  (
    SELECT
      [start].[Timestamp] AS [Start],
      [start].[Status] AS [StartingStatus],
      [end].[Timestamp] AS [End],
      [end].[Status] AS [EndingStatus]
    FROM [Alerts] [start],  [Alerts] [end]
    WHERE [start].[Status] = 2 
      AND [start].[Timestamp] < [end].[Timestamp]
      AND [start].[Status] <> [end].[Status]
  ) AS [sub]
  GROUP BY
    [sub].[Start],
    [sub].[StartingStatus]
) AS [main]
GROUP BY
  [main].[End]

这是一个Fiddle

答案 3 :(得分:-1)

我通过使用与表格相同的身份来做类似的事情。

    create table test(id int primary key identity(1,1),timstamp datetime,val int)

    insert into test(timstamp,val) Values('1/1/2013 00:00:00',1)
    insert into test(timstamp,val) Values('1/1/2013 00:00:05',2)
    insert into test(timstamp,val) Values('1/1/2013 00:00:25',1)
    insert into test(timstamp,val) Values('1/1/2013 00:00:30',2)
    insert into test(timstamp,val) Values('1/1/2013 00:00:35',1)

    select t1.timstamp,t1.val,DATEDIFF(s,t1.timstamp,t2.timstamp) 
    from test t1 left join test t2 on t1.id=t2.id-1

    drop table test

我还会将时间戳设为自1980年或2000年以来的秒数等等。但是你可能不想一直进行反向转换,所以这取决于你使用实际时间戳的频率。