patd1
asked on
tsql to split a time period in different months
Using MS Sql Server 2008.
I have a time period table with start and end dates. I need to calculate duration (month wise) for each time period. If the end date is null, then use current date as end date. If period between start and end date spans multiple months then I need to breakup duration in different months as shown in sample target data below.
How can I do this using t-sql, preferably without using cursor.
Source data (Insert statements copied below):
ID StartDate EndDate
1 9/1/2014 12/3/2014
2 11/9/2014 11/30/2014
3 12/9/2014 null
Target data:
ID StartDate EndDate Duration
1 9/1/2014 10/1/2014 30
1 10/1/2014 11/1/2014 31
1 11/1/2014 12/1/2014 30
1 12/1/2014 12/3/2014 2
2 11/9/2014 11/30/2014 21
3 12/9/2014 12/18/2014 9
Here is the sql for setting some sample source data in TimePeriod.
Thank you for your help in advance.
I have a time period table with start and end dates. I need to calculate duration (month wise) for each time period. If the end date is null, then use current date as end date. If period between start and end date spans multiple months then I need to breakup duration in different months as shown in sample target data below.
How can I do this using t-sql, preferably without using cursor.
Source data (Insert statements copied below):
ID StartDate EndDate
1 9/1/2014 12/3/2014
2 11/9/2014 11/30/2014
3 12/9/2014 null
Target data:
ID StartDate EndDate Duration
1 9/1/2014 10/1/2014 30
1 10/1/2014 11/1/2014 31
1 11/1/2014 12/1/2014 30
1 12/1/2014 12/3/2014 2
2 11/9/2014 11/30/2014 21
3 12/9/2014 12/18/2014 9
Here is the sql for setting some sample source data in TimePeriod.
Create table #timeperiod
(ID int identity(1,1)not null,
StartDate date not null,
EndDate date null)
Insert into #timeperiod
(StartDate,EndDate)
Values ('9/1/2014','12/3/2014')
Insert into #timeperiod
(StartDate,EndDate)
Values ('11/9/2014','11/30/2014')
Insert into #timeperiod
(StartDate,EndDate)
Values ('12/9/2014',null)
Thank you for your help in advance.
oops, i guess you need more than that!
Here you go:
drop table #timeperiod
drop table #mydates
go
Create table #timeperiod
(ID int identity(1,1)not null,
StartDate date not null,
EndDate date null)
Insert into #timeperiod
(StartDate,EndDate)
Values ('9/1/2014','12/3/2014')
Insert into #timeperiod
(StartDate,EndDate)
Values ('11/9/2014','11/30/2014')
Insert into #timeperiod
(StartDate,EndDate)
Values ('12/9/2014',null)
create table #mydates
(StartDate date not null,
EndDate date not null)
insert into #mydates
select top(datediff(month,(select min(StartDate) from #timeperiod),SYSDATETIME())+1)
dateadd(month,-1+row_number() over(order by t1.id),'9/1/2014') as StartDate,
dateadd(month,row_number() over(order by t1.id),'9/1/2014') as FinishDate
from #timeperiod t1
cross join #timeperiod t2
Select * from #mydates;
with myTable as (select T.ID, D.StartDate as MonthStart, D.EndDate as MonthEnd, T.StartDate, T.EndDate
from #mydates D
cross join #timeperiod T
where D.EndDate >= T.StartDate
and (D.startDate <= T.EndDate or T.EndDate is null)),
myTable2 as (Select ID,
case when MonthStart < StartDate then
StartDate else MonthStart end as FirstDate,
case when EndDate is null then getdate()
when EndDate < MonthEnd then EndDate else MonthEnd end as SecondDate
from myTable)
select ID, FirstDate as StartDate, SecondDate as EndDate, Datediff(day,FirstDate,SecondDate) as Duration
from myTable2
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thank you ScottPletcher. The solution is working for me.
Can you please explain what is going on in the crossApply (portion of code copied below)
Can you please explain what is going on in the crossApply (portion of code copied below)
SELECT CASE WHEN t.tally = 0 THEN tp.StartDate
ELSE DATEADD(MONTH, DATEDIFF(MONTH, 0, tp.StartDate) + t.tally, 0) END AS StartDate,
CASE WHEN t.tally = DATEDIFF(MONTH, tp.StartDate, ISNULL(tp.EndDate, GETDATE())) THEN ISNULL(tp.EndDate, GETDATE())
ELSE DATEADD(DAY, -1, DATEADD(MONTH, DATEDIFF(MONTH, 0, tp.StartDate) + t.tally + 1, 0)) END AS EndDate
Sure.
First, as background, the CROSS APPLY as used above basically just assigns aliases to calculated values. Otherwise, the entire expressions for StartDate and EndDate would have to be repeated when computing the Duration. The CROSS APPLY in effect creates another column we can then use anywhere in our main query. This example is somewhat more confusing than normal since the column names are exactly the same as the input column names, but it's actually a completely different value.
A stand-alone example may be clearer:
SELECT value, value1, value2
--note that I can select all the different CROSS APPLY values
FROM ( SELECT 17 AS value ) AS test_data
CROSS APPLY (
--multiply the original column value by 2 and give it a new alias name that can then be used anywhere in the query
SELECT value * 2 AS value1
) AS cross_apply_1
CROSS APPLY (
--note how we're using an earlier CROSS APPLY alias in this CA, which is an extremely useful capability!
SELECT value1 * 2 AS value2
) AS cross_apply_2
ORDER BY value1 --note that I can ORDER BY any CA value(s), not just the last one(s)
Back to the original query. The key ID is one like #1, which spans multiple months:
ID StartDate EndDate
1 9/1/2014 12/3/2014
The output needs to be:
ID StartDate EndDate Duration
1 9/1/2014 10/1/2014 30
1 10/1/2014 11/1/2014 31
1 11/1/2014 12/1/2014 30
1 12/1/2014 12/3/2014 2
Here's how the query does this as it executes:
1) The INNER JOIN on tally will be for values 0, 1, 2 and 3, since the "DATEDIFF(MONTH" from StartDate to EndDate is 3 months. Thus, a total of 4 rows will be output from the query, one for each month -- exactly what we want.
2) Since the output StartDate of the first row is the exact starting date, but for later months it is always the first of the month, when computing the new StartDate value in the CROSS APPLY we need to use "WHEN t.tally = 0" to test for the first row.
3) Similarly, since the output EndDate for the last row is the exact ending date, but for earlier months it is always the last day of the month, when computing the new EndDate value in the CROSS APPLY we need to use "WHEN t.tally = DATEDIFF(MONTH, tp.StartDate, ISNULL(tp.EndDate, GETDATE())) " to test for the last row.
First, as background, the CROSS APPLY as used above basically just assigns aliases to calculated values. Otherwise, the entire expressions for StartDate and EndDate would have to be repeated when computing the Duration. The CROSS APPLY in effect creates another column we can then use anywhere in our main query. This example is somewhat more confusing than normal since the column names are exactly the same as the input column names, but it's actually a completely different value.
A stand-alone example may be clearer:
SELECT value, value1, value2
--note that I can select all the different CROSS APPLY values
FROM ( SELECT 17 AS value ) AS test_data
CROSS APPLY (
--multiply the original column value by 2 and give it a new alias name that can then be used anywhere in the query
SELECT value * 2 AS value1
) AS cross_apply_1
CROSS APPLY (
--note how we're using an earlier CROSS APPLY alias in this CA, which is an extremely useful capability!
SELECT value1 * 2 AS value2
) AS cross_apply_2
ORDER BY value1 --note that I can ORDER BY any CA value(s), not just the last one(s)
Back to the original query. The key ID is one like #1, which spans multiple months:
ID StartDate EndDate
1 9/1/2014 12/3/2014
The output needs to be:
ID StartDate EndDate Duration
1 9/1/2014 10/1/2014 30
1 10/1/2014 11/1/2014 31
1 11/1/2014 12/1/2014 30
1 12/1/2014 12/3/2014 2
Here's how the query does this as it executes:
1) The INNER JOIN on tally will be for values 0, 1, 2 and 3, since the "DATEDIFF(MONTH" from StartDate to EndDate is 3 months. Thus, a total of 4 rows will be output from the query, one for each month -- exactly what we want.
2) Since the output StartDate of the first row is the exact starting date, but for later months it is always the first of the month, when computing the new StartDate value in the CROSS APPLY we need to use "WHEN t.tally = 0" to test for the first row.
3) Similarly, since the output EndDate for the last row is the exact ending date, but for earlier months it is always the last day of the month, when computing the new EndDate value in the CROSS APPLY we need to use "WHEN t.tally = DATEDIFF(MONTH, tp.StartDate, ISNULL(tp.EndDate, GETDATE())) " to test for the last row.
Open in new window