展平/合并重叠的时间间隔

时间:2014-01-30 22:20:15

标签: sql-server sql-server-2008 group-by

我有一个'服务'有数百万行的表。每行对应于工作人员在给定日期和时间间隔内提供的服务(每行具有唯一ID)。在某些情况下,工作人员可能会在重叠的时间范围内提供服务。我需要编写一个合并重叠时间间隔的查询,并以下面显示的格式返回数据。

我尝试按StaffID和Date字段进行分组,并获得开始时间的最小值和结束时间的最大值,但这并不考虑非重叠的时间范围。我怎么能做到这一点?同样,该表包含数百万条记录,因此递归CTE方法可能会出现性能问题。提前谢谢。

服务表

ID    StaffID  Date        BeginTime EndTime
1     101      2014-01-01  08:00     09:00
2     101      2014-01-01  08:30     09:30
3     101      2014-01-01  18:00     20:30
4     101      2014-01-01  19:00     21:00

输出

StaffID Date        BeginTime EndTime
101     2014-01-01  08:00     09:30
101     2014-01-01  18:00     21:00

这是另一个样本数据集,其中包含由贡献者提出的查询。 http://sqlfiddle.com/#!6/bfbdc/3

结果集中的前两行应合并为一行(06:00-08:45),但它会生成两行(06:00-08:30&amp; 06:00-08:45)< / p>

2 个答案:

答案 0 :(得分:2)

我只想出了一个CTE查询,因为问题是可能存在一系列重叠时间,例如:记录1与记录2重叠,记录2与记录3重叠,依此类推。如果没有CTE或其他类型的循环等,这很难解决。无论如何,请试一试。

CTE查询的第一部分获取启动新组的服务,并且与其他服务没有相同的开始时间(我只需要一条启动组的记录)。第二部分是那些开始组的人,但是有多个人有相同的开始时间 - 再次,我只需要其中一个。最后一部分递归地建立起始组,采用所有重叠的服务。

以下是SQLFiddle,其中添加了更多记录以展示不同类型的重叠和重复时间。

我无法使用ServiceID,因为它必须以与BeginTime相同的方式进行排序。

;with flat as
(
 select StaffID, ServiceDate, BeginTime, EndTime, BeginTime as groupid 
 from services S1
 where not exists (select * from services S2 
 where S1.StaffID = S2.StaffID 
 and S1.ServiceDate = S2.ServiceDate 
 and S2.BeginTime <= S1.BeginTime and S2.EndTime <> S1.EndTime
 and S2.EndTime > S1.BeginTime)

  union all

  select StaffID, ServiceDate, BeginTime, EndTime, BeginTime as groupid 
  from services S1
 where exists (select * from services S2 
 where S1.StaffID = S2.StaffID 
 and S1.ServiceDate = S2.ServiceDate 
 and S2.BeginTime = S1.BeginTime and S2.EndTime > S1.EndTime)
   and not exists (select * from services S2 
 where S1.StaffID = S2.StaffID 
 and S1.ServiceDate = S2.ServiceDate 
 and S2.BeginTime < S1.BeginTime
 and S2.EndTime > S1.BeginTime)

 union all

 select S.StaffID, S.ServiceDate, S.BeginTime, S.EndTime, flat.groupid 
 from flat
 inner join services S 
 on flat.StaffID = S.StaffID
 and flat.ServiceDate = S.ServiceDate
 and flat.EndTime > S.BeginTime
 and flat.BeginTime < S.BeginTime and flat.EndTime < S.EndTime
)

select StaffID, ServiceDate, MIN(BeginTime) as begintime, MAX(EndTime) as endtime 
from flat
group by StaffID, ServiceDate, groupid
order by StaffID, ServiceDate, begintime, endtime

答案 1 :(得分:0)

Elsewhere我也回答了类似的日期打包问题 几何策略。就是说,我解释了日期范围 作为一行,并利用geometry::UnionAggregate进行合并 范围。

您的问题有两个特点。首先,它调用 对于sql-server-2008。 geometry::UnionAggregate不是那时 可用的。但是,请从以下网址下载Microsoft库: https://github.com/microsoft/SQLServerSpatialTools并加载 将其作为实例的clr组件插入,就可以了 可作为dbo.GeometryUnionAggregate使用。

但是我感兴趣的是真正的特殊性 您有几百万行可以使用。所以我认为 我在这里重复了该策略,但是增加了一种技巧 改善其性能。如果这种技术可以很好地工作 您有很多相同的StaffID /日期子集。


首先,让我们建立一个数字表。与您最喜欢的交换 做到的方式。

select  i = row_number() over (order by (select null)) 
into    #numbers
from    @services; -- where i put your data

然后将日期转换为浮点数,并使用这些浮点数创建 几何点。

然后可以通过STUnion和STEnvelope将这些点转换为线。

现在将范围表示为几何线,通过 UnionAggregate。生成的几何对象“线”可能包含 多行。但是任何重叠的线会变成一条线。

select      s.StaffID, 
            s.Date,
            linesWKT = geometry::UnionAggregate(line).ToString() 

            -- If you have SQLSpatialTools installed then:
            -- linesWKT = dbo.GeometryUnionAggregate(line).ToString() 

into        #aggregateRangesToGeo
from        @services s
cross apply (select 
                beginTimeF = convert(float, convert(datetime,beginTime)),
                endTimeF = convert(float, convert(datetime,endTime))
            ) prepare
cross apply (select
                beginPt = geometry::Point(beginTimeF, 0, 0),
                endPt = geometry::Point(endTimeF, 0, 0)
            ) pointify
cross apply (select 
                line = beginPt.STUnion(endPt).STEnvelope()
            ) lineify
group by    s.StaffID,
            s.Date;

每个staffId / date组合都有一个“行”对象。但是取决于 在您的数据集上,可能有许多相同的“线”对象 在这些连击之间。如果期望有工作人员,这可能是正确的 遵循常规,数据被记录到最近的地方。

因此得到“直线”对象的明显区别。这应该改善 性能。

由此,提取“行”内的各个行。包围线, 这样可以确保仅将线存储为端点。阅读 端点x值并将其转换回其时间表示形式。

保留WKT表示,以后再将其加入组合。

select      lns.linesWKT,
            beginTime = convert(time, convert(datetime, ap.beginTime)),
            endTime = convert(time, convert(datetime, ap.endTime))
into        #parsedLines
from        (select distinct linesWKT from #aggregateRangesToGeo) lns
cross apply (select 
                lines = geometry::STGeomFromText(linesWKT, 0)
            ) geo
join        #numbers n on n.i between 1 and geo.lines.STNumGeometries()
cross apply (select 
                line = geo.lines.STGeometryN(n.i).STEnvelope()
            ) ln
cross apply (select 
                beginTime = ln.line.STPointN(1).STX,
                endTime = ln.line.STPointN(3).STX
            ) ap;

现在,只需将已解析的数据重新加入StaffId / Date组合即可。

select      ar.StaffID,
            ar.Date,
            pl.beginTime, 
            pl.endTime
from        #aggregateRangesToGeo ar
join        #parsedLines pl on ar.linesWKT = pl.linesWKT
order by    ar.StaffID, 
            ar.Date,
            pl.beginTime;