使用开始日期和结束日期将日期分为间隔

时间:2018-11-15 07:53:07

标签: sql sql-server tsql datetime sql-server-2008-r2

在某些情况下,我需要将给定的日期范围划分为每月间隔。

例如,输入如下:

StartDate   EndDate
2018-01-21  2018-01-29
2018-01-30  2018-02-23
2018-02-24  2018-03-31
2018-04-01  2018-08-16
2018-08-17  2018-12-31

预期的输出应如下所示:

StartDate   EndDate
2018-01-21  2018-01-29
2018-01-30  2018-01-31
2018-02-01  2018-02-23
2018-02-24  2018-02-28
2018-03-01  2018-03-31
2018-04-01  2018-04-30
2018-05-01  2018-05-31
2018-06-01  2018-06-30
2018-07-01  2018-07-31
2018-08-01  2018-08-16
2018-08-17  2018-08-31
2018-09-01  2018-09-30
2018-10-01  2018-10-31
2018-11-01  2018-11-30
2018-12-01  2018-12-31

下面是示例数据。

CREATE TABLE #Dates
(
    StartDate DATE,
    EndDate DATE
);


INSERT INTO #Dates
(
    StartDate,
    EndDate
)
VALUES
('2018-01-21', '2018-01-29'),
('2018-01-30', '2018-02-23'),
('2018-02-24', '2018-03-31'),
('2018-04-01', '2018-08-16'),
('2018-08-17', '2018-12-31');

4 个答案:

答案 0 :(得分:2)

您可以使用递归CTE。基本思想是从第一个日期2018-01-21开始,并建立直到最后一个日期2018-12-31的所有月份的开始和结束日期的列表。然后将数据与内部连接起来,并在必要时确定日期。

DECLARE @Dates TABLE (StartDate DATE, EndDate DATE);
INSERT INTO @Dates (StartDate, EndDate) VALUES
('2018-01-21', '2018-01-29'),
('2018-01-30', '2018-02-23'),
('2018-02-24', '2018-03-31'),
('2018-04-01', '2018-08-16'),
('2018-08-17', '2018-12-31');

WITH minmax AS (
    -- clamp min(start date) to 1st day of that month
    SELECT DATEADD(MONTH, DATEDIFF(MONTH, CAST('00010101' AS DATE), MIN(StartDate)), CAST('00010101' AS DATE)) AS mindate, MAX(EndDate) AS maxdate
    FROM @Dates
), months AS (
    -- calculate first and last day of each month
    -- e.g. for February 2018 it'll return 2018-02-01 and 2018-02-28
    SELECT mindate AS date01, DATEADD(DAY, -1, DATEADD(MONTH, 1, mindate)) AS date31, maxdate
    FROM minmax
    UNION ALL
    SELECT DATEADD(MONTH, 1, prev.date01), DATEADD(DAY, -1, DATEADD(MONTH, 2, prev.date01)), maxdate
    FROM months AS prev
    WHERE prev.date31 < maxdate
)
SELECT
    -- clamp start and end date to first and last day of corresponding month
    CASE WHEN StartDate < date01 THEN date01 ELSE StartDate END,
    CASE WHEN EndDate > date31 THEN date31 ELSE EndDate END
FROM months
INNER JOIN @Dates ON date31 >= StartDate AND EndDate >= date01

如果不选择rCTE,则您始终可以与数字表或日期表联接(上面的想法仍然适用)。

答案 1 :(得分:1)

您可以与Master..spt_values表进行交叉申请,以获取StartDate和EndDate之间每个月的一行。

SELECT * 
into #dates
FROM (values 
('2018-01-21', '2018-01-29')
,('2018-01-30', '2018-02-23')
,('2018-02-24', '2018-03-31')
,('2018-04-01', '2018-08-16')
,('2018-08-17', '2018-12-31')
)d(StartDate  , EndDate)



SELECT
    SplitStart as StartDate 
    ,case when enddate < SplitEnd then enddate else SplitEnd end as EndDate
FROM  #dates d
cross apply (
    SELECT 
        cast(dateadd(mm, number, dateadd(dd, (-datepart(dd, d.startdate) +1) * isnull((number / nullif(number, 0)), 0), d.startdate)) as date) as SplitStart
        ,cast(dateadd(dd, -datepart(dd, dateadd(mm, number+1, startdate)), dateadd(mm, number+1, startdate)) as date) as SplitEnd
    FROM 
    master..spt_values 
    where type = 'p' 
      and number between 0 and (((year(enddate) - year(startdate)) * 12) +  month(enddate) - month(startdate))   
) s

drop table #dates

答案 2 :(得分:0)

  

以下内容也应该起作用

     
      
  1. 首先,我将开始日期和结束日期放在cte-block数据的单列中。
  2.   
  3. 在som_eom块中,我创建了所有12个月的start_of_month和end_of_month。
  4.   
  5. 我将步骤1和2合并为curated_set
  6.   
  7. 我创建由日期列排序的curated_set
  8.   
  9. 最后,我拒绝了不需要的记录,在我的过滤器子句中不是in('som','StartDate')
  10.   
with data
   as (select *
         from dates
        unpivot(x for y in(startdate,enddate))t
       )
    ,som_eom
      as (select top 12
                 cast('2018-'+cast(row_number() over(order by (select null)) as varchar(2))+'-01' as date) as som
                 ,dateadd(dd
                          ,-1
                           ,dateadd(mm
                                    ,1
                                    ,cast('2018-'+cast(row_number() over(order by (select null)) as varchar(2))+'-01' as date)
                                    )
                           ) as eom
                from information_schema.tables
            )
      ,curated_set
        as(select *
             from data
            union all
            select *
              from som_eom
            unpivot(x for y in(som,eom))t
            )
       ,curated_data
         as(select x
                  ,y
                  ,lag(x) over(order by x) as prev_val
              from curated_set
             )
select prev_val as st_dt,x as end_dt
       ,y  
  from curated_Data
where y not in('som','StartDate')

答案 3 :(得分:0)

从初始StartDate开始并计算月末,如果在同一月内,则只需使用EndDate。 使用新计算的EndDate + 1作为StartDate进行递归,然后重复计算。

WITH cte AS 
 ( SELECT StartDate, -- initial start date
      CASE WHEN EndDate < DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH,0,StartDate)+1,0))
           THEN EndDate
           ELSE           DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH,0,StartDate)+1,0))
      END AS newEnd, -- LEAST(end of current month, EndDate)
      EndDate
   FROM #Dates

   UNION ALL

   SELECT dateadd(DAY,1,newEnd), -- previous end + 1 day, i.e. 1st of current month
      CASE WHEN EndDate <= DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH,0,StartDate)+2,0)) 
           THEN EndDate 
           ELSE            DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH,0,StartDate)+2,0)) 
      END, -- LEAST(end of next month, EndDate)
      EndDate
   FROM cte
   WHERE newEnd < EndDate 
 )
SELECT StartDate, newEnd 
FROM cte