Sql 合并连续日期范围
使用SQL Server 2008 R2 我试图将日期范围合并到最大日期范围中,因为一个结束日期位于下一个开始日期的旁边 数据是关于不同的就业情况。一些员工可能已经结束了他们的工作,并在稍后重新加入。这些应被视为两种不同的工作示例ID 5。有些人有不同类型的工作,在结束日期和开始日期并驾齐驱,在这种情况下,应将其视为示例ID 30中的一项工作 未结束的雇佣期的结束日期为空 一些例子可能很有启发性:Sql 合并连续日期范围,sql,tsql,sql-server-2008-r2,Sql,Tsql,Sql Server 2008 R2,使用SQL Server 2008 R2 我试图将日期范围合并到最大日期范围中,因为一个结束日期位于下一个开始日期的旁边 数据是关于不同的就业情况。一些员工可能已经结束了他们的工作,并在稍后重新加入。这些应被视为两种不同的工作示例ID 5。有些人有不同类型的工作,在结束日期和开始日期并驾齐驱,在这种情况下,应将其视为示例ID 30中的一项工作 未结束的雇佣期的结束日期为空 一些例子可能很有启发性: declare @t as table (employmentid int, startdate
declare @t as table (employmentid int, startdate datetime, enddate datetime)
insert into @t values
(5, '2007-12-03', '2011-08-26'),
(5, '2013-05-02', null),
(30, '2006-10-02', '2011-01-16'),
(30, '2011-01-17', '2012-08-12'),
(30, '2012-08-13', null),
(66, '2007-09-24', null)
-- expected outcome
EmploymentId StartDate EndDate
5 2007-12-03 2011-08-26
5 2013-05-02 NULL
30 2006-10-02 NULL
66 2007-09-24 NULL
我一直在尝试不同的孤岛和缺口技术,但没能破解这个 在我使用日期“31211231”时,你会看到奇怪的一点,那就是处理无终止日期场景的日期非常大。我假设每个员工不会有很多日期范围,所以我使用了一个简单的递归公共表表达式来组合这些范围 为了让它运行得更快,起始锚查询只保留那些不会链接到每个员工先前范围的日期。剩下的只是在枣树上行走,并不断扩大种植范围。最后一组只保留每个起始锚employmentid、startdate组合建立的最大日期范围 MS SQL Server 2008架构设置: 问题1: :
用于组合所有重叠时段的修改脚本。例如 01.01.2001-01.01.2010 05.05.2005-05.05.2015 将给出一个周期: 01.01.2001-05.05.2015 必须填写tbl.enddate
;WITH cte
AS(
SELECT
a.employmentid
,a.startdate
,a.enddate
from tbl a
left join tbl c on a.employmentid=c.employmentid
and a.startdate > c.startdate
and a.startdate <= dateadd(day, 1, c.enddate)
WHERE c.employmentid IS NULL
UNION all
SELECT
a.employmentid
,a.startdate
,a.enddate
from cte a
inner join tbl c on a.startdate=c.startdate
and (c.startdate = dateadd(day, 1, a.enddate) or (c.enddate > a.enddate and c.startdate <= a.enddate))
)
select distinct employmentid,
startdate,
nullif(max(enddate),'31.12.2099') enddate
from cte
group by employmentid, startdate
使用窗口函数而不是递归CTE的替代解决方案 选择 雇员ID, MINstartdate作为起始日期, NULLIFMAXCOALESCEenddate、'9999-01-01、'9999-01-01'作为enddate 从…起 选择 雇员ID, 开始日期, 结束日期, 日期添加 白天 -结合 SUMDATEDIFFDAY、startdate、enddate+1按employmentid分区按startdate排序在无界的前一行和前一行之间, 0 , 起始日期 作为玻璃钢 来自@t withGroup 按employmentid分组,grp 按employmentid、startdate排序 这是通过计算所有连续行的grp值来实现的。这是通过以下方式实现的: 确定跨度占+1的总天数,因为日期包括在内 选择*、DATEDIFFDAY、startdate、enddate+1作为从@t平移的日期 按startdate排序的每次雇佣的累计天数。这为我们提供了以前所有雇佣期的总天数 我们与0合并,以确保我们的累计天数总和中没有空值 我们不将当前行包括在累积总和中,这是因为我们将对startdate而不是enddate使用该值。由于为空,我们无法对enddate使用该值 选择*,合并 萨姆代斯潘 按employmentid划分 按起始日期订购 无界前置和1前置之间的行 ,0 随着累积量的增加 从…起 选择*、DATEDIFFDAY、startdate、enddate+1作为从@t平移的日期 内部1 从起始日期减去累计天数,得到我们的grp。这是解决问题的关键。 如果开始日期以与所跨越的天数相同的速率增加,那么这些天数是连续的,减去这两天将得到相同的值。 如果startdate的增长速度快于所跨越的天数,则存在差距,我们将获得一个新的grp值,该值大于前一个值。 尽管grp是一个日期,但日期本身毫无意义,我们只是将其用作分组值 选择*,DATEADDDAY,-CumulativeDaySpaned,startdate作为grp 从…起 选择*,合并 萨姆代斯潘 按employmentid划分 按起始日期订购 无界前置和1前置之间的行 ,0 随着累积量的增加 从…起 选择*、DATEDIFFDAY、startdate、enddate+1作为从@t平移的日期 内部1 内部2 结果如何
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| employmentid | startdate | enddate | daysSpanned | cumulativeDaysSpanned | grp |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 5 | 2007-12-03 00:00:00.000 | 2011-08-26 00:00:00.000 | 1363 | 0 | 2007-12-03 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 5 | 2013-05-02 00:00:00.000 | NULL | NULL | 1363 | 2009-08-08 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 30 | 2006-10-02 00:00:00.000 | 2011-01-16 00:00:00.000 | 1568 | 0 | 2006-10-02 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 30 | 2011-01-17 00:00:00.000 | 2012-08-12 00:00:00.000 | 574 | 1568 | 2006-10-02 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 30 | 2012-08-13 00:00:00.000 | NULL | NULL | 2142 | 2006-10-02 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 66 | 2007-09-24 00:00:00.000 | NULL | NULL | 0 | 2007-09-24 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
最后,我们可以按grp分组,以摆脱连续的几天。
使用“最小”和“最大”获取新的开始日期和结束日期
为了处理NULL enddate,我们给它们一个较大的值,以供MAX提取,然后再次将它们转换回NULL
选择
雇员ID,
MINstartdate作为起始日期,
NULLIFMAXCOALESCEenddate、'9999-01-01、'9999-01-01'作为enddate
从…起
选择*,DATEADDDAY,-CumulativeDaySpaned,startdate作为grp
从…起
选择*,合并
萨姆代斯潘
按employmentid划分
按起始日期订购
无界前置和1前置之间的行
,0
随着累积量的增加
从…起
选择*、DATEDIFFDAY、startdate、enddate+1作为日期平移自@
T
内部1
内部2
内部3
按employmentid分组,grp
按employmentid、startdate排序
达到预期的效果
+--------------+-------------------------+-------------------------+
| employmentid | startdate | enddate |
+--------------+-------------------------+-------------------------+
| 5 | 2007-12-03 00:00:00.000 | 2011-08-26 00:00:00.000 |
+--------------+-------------------------+-------------------------+
| 5 | 2013-05-02 00:00:00.000 | NULL |
+--------------+-------------------------+-------------------------+
| 30 | 2006-10-02 00:00:00.000 | NULL |
+--------------+-------------------------+-------------------------+
| 66 | 2007-09-24 00:00:00.000 | NULL |
+--------------+-------------------------+-------------------------+
我们可以组合内部查询,在这个答案的开头得到查询。它比较短,但不太容易解释
所有这些的局限性要求
就业的开始日期和结束日期没有重叠。这可能会在我们的grp中产生碰撞。
startdate不为空。但是,可以通过用较小的日期值替换空的开始日期来克服这一问题
未来的开发人员可以破译您执行的窗口黑魔法
startDate==endDate是否应该进行适当的重叠?否则将有24小时无法解释。这将是存储过程,是吗?还是您的查询限制?@MaxH:实际上,日期时间是用作日期的。所以重叠是可以的。@JonasLincoln:是的,我理解,但是如果你计算一个员工被雇佣的天数,你会得到不同的结果。在上面的示例中,employmentid 30工作了1567+573+234=2374天,null=2013-04-04=今天。这与2006-10-02至2013-04-04的employmentid 30 2376天的汇总不同。对于每一个就业类型的改变,你将少1天。不要提供简单的代码,而是尝试解释思维过程,让所有人都受益,寻找答案。逻辑似乎是这样的:在所有范围合并后,合并范围组中的第一个范围有一个开始日期,而不在任何其他范围内,组中的最后一个范围的结束日期不在其他范围内。查询查找所有前一个范围s1,并查找对应的最后一个范围MINt1。ToDate对应于在s1之后结束的最早的最后一个范围。EXISTS条件将s1限制在第一个范围内,t1限制在最后一个范围内。六年后,对于小日期组来说,这仍然是一个极好的解决方案。非常感谢。cte中的第一个投影不应该是;选择cte作为a.employmentid,b.startdate,a.enddate。b、 开始日期而不是开始日期?
;WITH cte
AS(
SELECT
a.employmentid
,a.startdate
,a.enddate
from tbl a
left join tbl c on a.employmentid=c.employmentid
and a.startdate > c.startdate
and a.startdate <= dateadd(day, 1, c.enddate)
WHERE c.employmentid IS NULL
UNION all
SELECT
a.employmentid
,a.startdate
,a.enddate
from cte a
inner join tbl c on a.startdate=c.startdate
and (c.startdate = dateadd(day, 1, a.enddate) or (c.enddate > a.enddate and c.startdate <= a.enddate))
)
select distinct employmentid,
startdate,
nullif(max(enddate),'31.12.2099') enddate
from cte
group by employmentid, startdate
SET NOCOUNT ON
DECLARE @T TABLE(ID INT,FromDate DATETIME, ToDate DATETIME)
INSERT INTO @T(ID,FromDate,ToDate)
SELECT 1,'20090801','20090803' UNION ALL
SELECT 2,'20090802','20090809' UNION ALL
SELECT 3,'20090805','20090806' UNION ALL
SELECT 4,'20090812','20090813' UNION ALL
SELECT 5,'20090811','20090812' UNION ALL
SELECT 6,'20090802','20090802'
SELECT ROW_NUMBER() OVER(ORDER BY s1.FromDate) AS ID,
s1.FromDate,
MIN(t1.ToDate) AS ToDate
FROM @T s1
INNER JOIN @T t1 ON s1.FromDate <= t1.ToDate
AND NOT EXISTS(SELECT * FROM @T t2
WHERE t1.ToDate >= t2.FromDate
AND t1.ToDate < t2.ToDate)
WHERE NOT EXISTS(SELECT * FROM @T s2
WHERE s1.FromDate > s2.FromDate
AND s1.FromDate <= s2.ToDate)
GROUP BY s1.FromDate
ORDER BY s1.FromDate
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| employmentid | startdate | enddate | daysSpanned | cumulativeDaysSpanned | grp |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 5 | 2007-12-03 00:00:00.000 | 2011-08-26 00:00:00.000 | 1363 | 0 | 2007-12-03 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 5 | 2013-05-02 00:00:00.000 | NULL | NULL | 1363 | 2009-08-08 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 30 | 2006-10-02 00:00:00.000 | 2011-01-16 00:00:00.000 | 1568 | 0 | 2006-10-02 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 30 | 2011-01-17 00:00:00.000 | 2012-08-12 00:00:00.000 | 574 | 1568 | 2006-10-02 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 30 | 2012-08-13 00:00:00.000 | NULL | NULL | 2142 | 2006-10-02 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
| 66 | 2007-09-24 00:00:00.000 | NULL | NULL | 0 | 2007-09-24 00:00:00.000 |
+--------------+-------------------------+-------------------------+-------------+-----------------------+-------------------------+
+--------------+-------------------------+-------------------------+
| employmentid | startdate | enddate |
+--------------+-------------------------+-------------------------+
| 5 | 2007-12-03 00:00:00.000 | 2011-08-26 00:00:00.000 |
+--------------+-------------------------+-------------------------+
| 5 | 2013-05-02 00:00:00.000 | NULL |
+--------------+-------------------------+-------------------------+
| 30 | 2006-10-02 00:00:00.000 | NULL |
+--------------+-------------------------+-------------------------+
| 66 | 2007-09-24 00:00:00.000 | NULL |
+--------------+-------------------------+-------------------------+