Sql 合并连续日期范围

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

使用SQL Server 2008 R2

我试图将日期范围合并到最大日期范围中,因为一个结束日期位于下一个开始日期的旁边

数据是关于不同的就业情况。一些员工可能已经结束了他们的工作,并在稍后重新加入。这些应被视为两种不同的工作示例ID 5。有些人有不同类型的工作,在结束日期和开始日期并驾齐驱,在这种情况下,应将其视为示例ID 30中的一项工作

未结束的雇佣期的结束日期为空

一些例子可能很有启发性:

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                    |
+--------------+-------------------------+-------------------------+