在 SQL Server 中拼接日期期间
Posted
技术标签:
【中文标题】在 SQL Server 中拼接日期期间【英文标题】:Splicing Date Periods in SQL Server 【发布时间】:2015-06-22 23:20:45 【问题描述】:我需要在 SQL Server 中合并日期期间的解决方案。
下面设置了工作示例。我有两张表,第一张为特定产品保存一个时期,第二张保存在第一张表相关期间内的较小时期。我需要将两者合并,以便在包含较小时期时得到涵盖原始时期的所有连续时期的列表。我已经包含了所需结果的示例。
规则:
每个产品的第一个表中只会有一行 第二个表中的行不会与产品重叠 第二个表中的行只会落在原始期间的边界上或之内 声明 @x 表(产品 varchar(10),从日期开始,到现在日期)——预测 声明 @y 表(产品 varchar(10),从日期开始,到现在日期)——购买 声明 @z 表(产品 varchar(10),从日期开始,到现在日期)——结果 插入@x 值('lrecs'、'20150101'、'20161231') 插入@x 值('srecs'、'20150701'、'20161231') 插入@y 值('lrecs'、'20150401'、'20150630') 插入@y 值('lrecs'、'20160101'、'20160630') 插入@y 值('srecs'、'20160101'、'20161231') /* 产品从今天开始 ------------------------------ lrecs 2015-01-01 2015-03-31 lrecs 2015-04-01 2015-06-30 lrecs 2015-07-01 2015-12-31 lrecs 2016-01-01 2016-06-30 lrecs 2016-07-01 2016-12-31 srecs 2015-07-01 2015-12-31 srecs 2016-01-01 2016-12-31 */【问题讨论】:
我正在研究使用嵌套游标的解决方案,但我正在努力解决它,我想知道是否有其他人做过类似的事情或对如何最好地攻击它有任何想法。跨度> 尝试使用 while 循环。它更快 @Sushil,这不是真的。WHILE
循环仍然是 CURSOR
。有关详细信息,请参阅此article。
您使用什么版本的 SQL Server?在 SQL Server 2012+ 中,LEAD
和 LAG
函数有助于有效地解决这个“差距和孤岛”问题。
感谢@wewestthemenace。这是一篇不错的文章。消除了我的误解。
【参考方案1】:
SQL 2012 及以上解决方案
我承认我没有对此进行广泛的测试以确保它适用于所有情况,但它适用于您的示例数据集。这是一个递归/循环/无光标的解决方案。检查一下,如果需要任何调整或有任何问题,请告诉我。
--Combines the tables
;WITH CTE_Union
AS
(
SELECT product,fromdate,todate
FROM @y
UNION ALL
SELECT product,fromdate,NULL
FROM @x
UNION ALL
SELECT product,NULL,todate
FROM @x
),
--forms most of the ranges
CTE_range
AS
(
SELECT *,
next_fromdate = LEAD(fromdate,1) OVER (PARTITION BY product ORDER BY fromdate)
FROM
(
SELECT product,
fromdate = COALESCE(fromdate,DATEADD(DAY,1,LAG(todate,1) OVER (PARTITION BY product ORDER BY todate))),
todate = COALESCE(todate,DATEADD(DAY,-1,LEAD(fromdate,1) OVER (PARTITION BY product ORDER BY fromdate)))
FROM CTE_Union
) A
WHERE fromdate < todate
)
SELECT product,
fromdate,
todate
FROM CTE_range
UNION ALL
--fills in the gaps
SELECT product,DATEADD(DAY,1,todate),DATEADD(DAY,-1,next_fromdate)
FROM CTE_range
WHERE DATEDIFF(DAY,todate,next_fromdate) != 1 --so where there is a gap
ORDER BY product,fromdate
【讨论】:
感谢 Stephan,我不熟悉 LEAD 和 LAG,这是一个比我想出的更优雅的解决方案。【参考方案2】:TrimmedRanges
cte 只是使用表 @x 来“修剪”表 @y 中的每个范围。然后CombinedRanges
cte 查找相邻范围并创建覆盖它们的新范围。它递归地执行此操作。最后,MostComprehensiveRanges
cte 仅提取其他范围未包含的范围。
请注意,根据您正在处理的数据量,您可能需要使用OPTION (MAXRECURSION ##)
查询提示。
https://msdn.microsoft.com/en-us/library/ms175972.aspx
declare @x table (product varchar(10), fromdate date, todate date) -- forecast
declare @y table (product varchar(10), fromdate date, todate date) -- purchases
declare @z table (product varchar(10), fromdate date, todate date) -- result
insert @x values ('lrecs', '20150101', '20161231')
insert @x values ('srecs', '20150701', '20161231')
insert @y values ('lrecs', '20141201', '20150331')
insert @y values ('lrecs', '20150401', '20150630')
insert @y values ('lrecs', '20150701', '20150731')
insert @y values ('lrecs', '20150801', '20150831')
insert @y values ('lrecs', '20160101', '20160630')
insert @y values ('srecs', '20160101', '20161231')
;with TrimmedRanges as (
select
a.product,
case when a.fromdate < b.fromdate then b.fromdate else a.fromdate end fromdate,
case when a.todate > b.todate then b.todate else a.todate end todate
from
@y a
join @x b on a.product = b.product
),
CombinedRanges as (
select product, fromdate, todate from TrimmedRanges
union all
select a.product, a.fromdate, b.todate
from
TrimmedRanges a
join CombinedRanges b
on a.product = b.product
and b.fromdate = dateadd(d, 1, a.todate)
),
MostComprehensiveRanges as (
select a.*
from CombinedRanges a
where
not exists (
select 1
from CombinedRanges b
where
a.product = b.product
and (
( a.fromdate > b.fromdate and a.fromdate < b.todate )
or ( a.todate > b.fromdate and a.todate < b.todate )
)
)
)
select * from MostComprehensiveRanges
order by product, fromdate
【讨论】:
谢谢 JC - 我没有花很多时间检查原因,但我只从您的解决方案中得到了三行。无论如何,我已经接受了 Stephan 的解决方案。以上是关于在 SQL Server 中拼接日期期间的主要内容,如果未能解决你的问题,请参考以下文章