从日志表构建范围表

时间:2014-10-29 19:53:24

标签: sql sql-server-2012 date-range

我有一个包含4列的日志表(MainIdChildIdUnixTimeIsStopped)。 我需要将这些数据插入到范围表中。 (MainIdChildIdStartUnixTimeStopUnixtime

要创建的范围的逻辑如下:

  • 日志表中UnixTimeIsStopped = 0的最小StartUnixTime始终是范围表中的第一个IsStopped
  • 在此之后构建范围的条件是:如果IsStopped = 1,那么这是范围的结束,而IsStopped = 0的下一行是新范围的开始

因为MainId并不总是设置另外两个条件:

  • 如果MainId从一行更改为另一行,则更改该行的行是范围的结尾,此行也是新范围的开头。
  • ChildId相同,适用于create function dbo.UnixToDate(@unixTime int) returns datetime as begin return dateadd(s, @unixTime, '1970-01-01') end go create table test ( Id Int Primary Key Identity, MainId int, ChildId int, UnixTime int, IsStoppped bit ) go insert into test (MainId, ChildId, UnixTime, IsStopped) values (1, 100, 1406028071, 0), (1, 100, 1406028073, 0), (1, 102, 1406028078, 0), (1, 102, 1406028080, 0), (1, 102, 1406028099, 0), (1, 100, 1406028130, 0), (1, 102, 1406028132, 0), (1, 102, 1406028134, 0), (1, 102, 1406028138, 0), (1, 100, 1406028140, 0), (1, 100, 1406028148, 1), (1, 100, 1406028150, 0), (2, 100, 1406028151, 0), (2, 100, 1406028152, 1), (2, 100, 1406028153, 1), (1, 100, 1406028155, 0) go create table expected ( MainId int, ChildId int, StartUnixTime int, StopUnixTime int ) go insert into expected(MainId, ChildId, StartUnixTime, StopUnixTime) values (1, 100, 1406028071, 1406028078), (1, 102, 1406028078, 1406028130), (1, 100, 1406028130, 1406028132), (1, 102, 1406028132, 1406028140), (1, 100, 1406028140, 1406028148), (1, 100, 1406028150, 1406028151), (2, 100, 1406028151, 1406028152), (1, 100, 1406028155, null) go select MainId, ChildId, UnixTime, dbo.UnixToDate(UnixTime), IsStopped from test order by UnixTime asc go --excpected select * from excpected order by StartUnixTime asc

我做了SqlFiddle

我们目前有一个包含游标的解决方案。但是这个速度非常慢,因为这个表大约有3000万行。

如果没有光标,我怎么能这样做?

更新

由于SqlFiddle今天确实存在问题(我无法构建任何模式只能获得xml错误)我在这里发布了脚本:

IsStopped

更新

要求已更改,我现在可以将日志表更改为始终包含{{1}}列。 我今天晚些时候更新我的问题以包含新的样本数据...我给出了解决方案的接受答案,该解决方案可以在最短的时间内创建这个范围表。

2 个答案:

答案 0 :(得分:0)

好的,2个小时后,这是

WITH cte as(select MainId,ChildID,
    (CASE WHEN LEAD(MainId,1,0) OVER(ORDER BY UnixTime)<>MainId THEN LEAD(UnixTime,1,0) OVER(ORDER BY UnixTime) END) as mainIdbreak


from test
UNION
SELECT Top 1 MainId,ChildID,MIN(Unixtime) OVER (ORDER  BY UnixTime) FROM test WHERE IsStopped=0
UNION
SELECT MainId,ChildID, (CASE WHEN LEAD(ChildId,1,0) OVER(ORDER BY UnixTime)<>ChildId THEN LEAD(UnixTime,1,0) OVER(ORDER BY UnixTime) END) as childIdbreak
from test
UNION
SELECT
MainId,ChildID,(CASE WHEN LEAD(IsStopped,1,0) OVER(ORDER BY UnixTime)<>IsStopped THEN LEAD(UnixTime,1,0) OVER(ORDER BY UnixTime) END) as IsStoppedbreak
from test)
SELECT MainId,ChildID, mainIdbreak,LEAD(mainIdbreak,1,0) OVER(ORDER BY mainIdbreak) FROM cte Where mainIdbreak<>0



1   100 1406028071  1406028078
1   100 1406028078  1406028130
1   102 1406028130  1406028132
1   100 1406028132  1406028140
1   102 1406028140  1406028148
1   100 1406028148  1406028150
1   100 1406028150  1406028151
1   100 1406028151  1406028152
2   100 1406028152  1406028155
2   100 1406028155  0

我认为您想要的结果有点偏离,结尾范围在某些地方的开始范围内不会重复。

答案 1 :(得分:0)

我可以看到为什么你会遇到大量行的性能问题。考虑使用unixTime / nextUnixTimeWhereIsStoppedEQ1创建一个表,这样就可以限制下一个停止时间考虑的行数,但是有多行,直到下一行isStopped = 1,而不是查询下一个匹配的所有3000万行。

我可以看到你为什么要使用游标找出要添加到日志表的内容。不使用光标听起来很吸引人,但是理解你在结束时考虑了很多垃圾行,而不仅仅是接下来的几行。当您的端点稀疏填充时,使用set函数更有意义。

同时了解你的udf会严重影响你的表现。确保你没有在你的循环中调用它,而只是在最后报告你的结果。

我将您的要求解释为:

start at min(unixtime) >> first_unixtime    
stop when 
     mainID <> mainid(first_unixtime) or 
     childID <> childid(first_unixtime) or 
     isstopped=1 or 
     eof    
  when isstopped=1, start on next row, else start on this row >> next_startunixtime
repeat with next_startunixtime

HTH