在SQL Server中拼接日期

时间:2015-06-22 23:20:46

标签: sql-server tsql

我需要一个在SQL Server中合并日期的解决方案。

以下设置工作示例。我有两个表,第一个持有特定产品的单个期间,第二个持有与第一个表相关的期间内的较小期间。我需要将两者合并,以便最终得到一个包含较小时期的原始时期所有连续时期的列表。我已经包含了所需结果的样本。

规则:

  • 每个产品的第一个表中只有一行
  • 第二个表中的行永远不会与产品重叠
  • 第二个表格中的行只会落在原始时期的边界内或之内
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', '20150401', '20150630')
insert  @y values ('lrecs', '20160101', '20160630')
insert  @y values ('srecs', '20160101', '20161231')

/*
product fromdate    todate
------------------------------
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
*/

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

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