在某些情况下,我需要将给定的日期范围划分为每月间隔。
例如,输入如下:
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');
答案 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)
以下内容也应该起作用
- 首先,我将开始日期和结束日期放在cte-block数据的单列中。
- 在som_eom块中,我创建了所有12个月的start_of_month和end_of_month。
- 我将步骤1和2合并为curated_set
- 我创建由日期列排序的curated_set
- 最后,我拒绝了不需要的记录,在我的过滤器子句中不是in('som','StartDate')
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