SQL Server 2014 合并重叠日期范围
Posted
技术标签:
【中文标题】SQL Server 2014 合并重叠日期范围【英文标题】:SQL Server 2014 Merging Overlapping Date Ranges 【发布时间】:2017-12-23 07:23:56 【问题描述】:我在 SQL Server 2014 数据库中有一个包含 200.000 行的表,如下所示:
CREATE TABLE DateRanges
(
Contract VARCHAR(8),
Sector VARCHAR(8),
StartDate DATE,
EndDate DATE
);
INSERT INTO DateRanges (Contract, Sector, StartDate, Enddate)
SELECT '111', '999', '01-01-2014', '03-31-2014'
union
SELECT '111', '999', '04-01-2014', '06-30-2014'
union
SELECT '111', '999', '07-01-2014', '09-30-2014'
union
SELECT '111', '999', '10-01-2014', '12-31-2014'
union
SELECT '111', '888', '08-01-2014', '08-31-2014'
union
SELECT '111', '777', '08-15-2014', '08-31-2014'
union
SELECT '222', '999', '01-01-2014', '03-31-2014'
union
SELECT '222', '999', '04-01-2014', '06-30-2014'
union
SELECT '222', '999', '07-01-2014', '09-30-2014'
union
SELECT '222', '999', '10-01-2014', '12-31-2014'
union
SELECT '222', '666', '11-01-2014', '11-30-2014'
UNION
SELECT '222', '555', '11-15-2014', '11-30-2014';
如您所见,每份合同可能有多个重叠,我想要的是这样的结果
Contract Sector StartDate EndDate
---------------------------------------------
111 999 01-01-2014 07-31-2014
111 888 08-01-2014 08-14-2014
111 777 08-15-2014 08-31-2014
111 999 09-01-2014 12-31-2014
222 999 01-01-2014 10-31-2014
222 666 11-01-2014 11-14-2014
222 555 11-15-2014 11-30-2014
222 999 12-01-2014 12-31-2014
我不知道如何做到这一点,而且我在这个网站上看到的例子完全不适合我的问题。
【问题讨论】:
EndDate '07-31-2014' 是如何计算的?它不在您的数据中。 该日期不在数据中。它应该计算为开始具有不同部门的新日期范围之前的最后日期 类似的问题已经被问过很多次了,例如: ***.com/questions/2561130/…你可以试试这篇文章更深入的解释如何解决它:experts-exchange.com/articles/3952/… 我已经尝试了以上两个链接,但我似乎并没有按照我想要的方式得到它。如果有人可以用代码示例指导我,我将不胜感激 【参考方案1】:这个答案使用了几种不同的技术。第一个是recursive-cte,它使用每个相关的cal_date
创建一个表,然后使用唯一的Contract
值获取cross apply
'd,以获得这两个值的每个组合。第二个是window-functions 如lag
和row_number
来确定下面cmets 中详述的各种事物。最后,可能也是最重要的一点,gaps-and-islands 确定一个 Contract
/Sector
组合何时结束,下一个组合何时开始。
答案:
--determine range of dates
declare @bgn_dt date = (select min(StartDate) from DateRanges)
, @end_dt date = (select max(EndDate) from DateRanges)
--use a recursive CTE to create a record for each day / Contract
; with dates as
(
select @bgn_dt as cal_date
union all
select dateadd(d, 1, a.cal_date) as cal_date
from dates as a
where a.cal_date < @end_dt
)
select d.cal_date
, c.Contract
into #contract_dates
from dates as d
cross apply (select distinct Contract from DateRanges) as c
option (maxrecursion 0)
--Final Select
select f.Contract
, f.Sector
, min(f.cal_date) as StartDate
, max(f.cal_date) as EndDate
from (
--Use the sum-over to obtain the Island Numbers
select dr.Contract
, dr.Sector
, dr.cal_date
, sum(dr.IslandBegin) over (partition by dr.Contract order by dr.cal_date asc) as IslandNbr
from (
--Determine if the record is the start of a new Island
select a.Contract
, a.Sector
, a.cal_date
, case when lag(a.Sector, 1, NULL) over (partition by a.Contract order by a.cal_date asc) = a.Sector then 0 else 1 end as IslandBegin
from (
--Determine which Contract/Date combinations are valid, and rank the Sectors that are in effect
select cd.cal_date
, dr.Contract
, dr.Sector
, dr.EndDate
, row_number() over (partition by dr.Contract, cd.cal_date order by dr.StartDate desc) as ConractSectorRnk
from #contract_dates as cd
left join DateRanges as dr on cd.Contract = dr.Contract
and cd.cal_date between dr.StartDate and dr.EndDate
) as a
where a.ConractSectorRnk = 1
and a.Contract is not null
) as dr
) as f
group by f.Contract
, f.Sector
, f.IslandNbr
order by f.Contract asc
, min(f.cal_date) asc
输出:
+----------+--------+------------+------------+
| Contract | Sector | StartDate | EndDate |
+----------+--------+------------+------------+
| 111 | 999 | 2014-01-01 | 2014-07-31 |
| 111 | 888 | 2014-08-01 | 2014-08-14 |
| 111 | 777 | 2014-08-15 | 2014-08-31 |
| 111 | 999 | 2014-09-01 | 2014-12-31 |
| 222 | 999 | 2014-01-01 | 2014-10-31 |
| 222 | 666 | 2014-11-01 | 2014-11-14 |
| 222 | 555 | 2014-11-15 | 2014-11-30 |
| 222 | 999 | 2014-12-01 | 2014-12-31 |
+----------+--------+------------+------------+
【讨论】:
感谢您的出色工作和麻烦回答我的问题以上是关于SQL Server 2014 合并重叠日期范围的主要内容,如果未能解决你的问题,请参考以下文章