Reputation: 1
I have table with month's ranges like this:
CREATE TABLE [dbo].[T_month_ranges](
[StartMonth] [datetime] NULL,
[EndMonth] [datetime] NULL
) ON [PRIMARY]
INSERT INTO [dbo].[T_month_ranges] ([StartMonth],[EndMonth]) VALUES
('2015-02-01','2015-04-01')
INSERT INTO [dbo].[T_month_ranges] ([StartMonth],[EndMonth]) VALUES
('2016-12-01','2017-02-01')
INSERT INTO [dbo].[T_month_ranges] ([StartMonth],[EndMonth]) VALUES
('2017-08-01','2017-09-01');
These dates represents only year and month, first day is not important here.
Now we need to create select, that returns all months between dates in this table. So in the query result would be dates like these:
2015-02-01;2015-03-01;2015-04-01;2016-12-01;2017-01-01;2017-02-01;2017-08-01;2017-09-01
What is the best approach to achieve this in sql server ?
Upvotes: 0
Views: 914
Reputation: 1269773
You can use a recursive CTE:
with m as (
select mr.startmonth as mon, mr.endmonth
from T_month_ranges mr
union all
select dateadd(month, 1, m.mon), m.endmonth
from m
where m.mon < m.endmonth
)
select m.mon
from m;
If your ranges are really wide (which seems unlikely with months), then you might need to set the MAXRECURSION
option to 0.
An alternative -- which is probably faster -- is to use a table of numbers:
with n as (
select row_number() over (order by (select null)) - 1 as n
from master..spt_values
)
select dateadd(month, n.n, mr.startmonth)
from T_month_ranges mr join
n
on dateadd(month, n.n, mr.startmonth) <= mr.endmonth;
Upvotes: 2
Reputation: 81960
I'll often use a TVF to create dynamic date/time ranges. A tally/table would do the trick as well. The function is parameter driven, you supply the Range, DatePart, and Increment
For example:
Select A.*
,B.*
From T_month_ranges A
Cross Apply [dbo].[udf-Range-Date](A.StartMonth,A.EndMonth,'MM',1) B
Returns
The UDF if interested.
CREATE FUNCTION [dbo].[udf-Range-Date] (@R1 datetime,@R2 datetime,@Part varchar(10),@Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case @Part When 'YY' then DateDiff(YY,@R1,@R2)/@Incr When 'QQ' then DateDiff(QQ,@R1,@R2)/@Incr When 'MM' then DateDiff(MM,@R1,@R2)/@Incr When 'WK' then DateDiff(WK,@R1,@R2)/@Incr When 'DD' then DateDiff(DD,@R1,@R2)/@Incr When 'HH' then DateDiff(HH,@R1,@R2)/@Incr When 'MI' then DateDiff(MI,@R1,@R2)/@Incr When 'SS' then DateDiff(SS,@R1,@R2)/@Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,@R1 Union All Select N,Case @Part When 'YY' then DateAdd(YY, N*@Incr, @R1) When 'QQ' then DateAdd(QQ, N*@Incr, @R1) When 'MM' then DateAdd(MM, N*@Incr, @R1) When 'WK' then DateAdd(WK, N*@Incr, @R1) When 'DD' then DateAdd(DD, N*@Incr, @R1) When 'HH' then DateAdd(HH, N*@Incr, @R1) When 'MI' then DateAdd(MI, N*@Incr, @R1) When 'SS' then DateAdd(SS, N*@Incr, @R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=@R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
Option 2 (without a UDF)
Select A.*
,B.*
From T_month_ranges A
Cross Apply (
Select Top (DateDiff(MM,A.Startmonth,A.EndMonth)+1)
Date=DateAdd(MM,Row_Number() over (Order by (Select null)) - 1,A.Startmonth)
From master..spt_values
) B
Upvotes: 0