我有一个'服务'有数百万行的表。每行对应于工作人员在给定日期和时间间隔内提供的服务(每行具有唯一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>
答案 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;