Splitting dates into intervals using Start Date and End Date
I have scenario where I need to split the given date range into monthly intervals.
For example, the input is like below:
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
And the expected output should be like below:
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
Below is the sample data.
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');
sql sql-server tsql datetime sql-server-2008-r2
add a comment |
I have scenario where I need to split the given date range into monthly intervals.
For example, the input is like below:
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
And the expected output should be like below:
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
Below is the sample data.
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');
sql sql-server tsql datetime sql-server-2008-r2
It's great you've posted sample data properly, but you should also post what you have tried so far. For more details, please read How to Ask.
– Zohar Peled
Nov 15 '18 at 8:09
Also, what if you have a value in theStartDate
that is the end of the month (like2018-01-31
)?
– Zohar Peled
Nov 15 '18 at 8:11
If the StartDate is 2018-01-31, Then the output should be StartDate - 2018-01-31 EndDate - 2018-01-31
– kkr
Nov 15 '18 at 8:17
This would be easily solved with a calendar table. Here is a (yet another) great article by Aaron Bertrand on how to create one.
– Zohar Peled
Nov 15 '18 at 8:46
add a comment |
I have scenario where I need to split the given date range into monthly intervals.
For example, the input is like below:
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
And the expected output should be like below:
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
Below is the sample data.
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');
sql sql-server tsql datetime sql-server-2008-r2
I have scenario where I need to split the given date range into monthly intervals.
For example, the input is like below:
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
And the expected output should be like below:
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
Below is the sample data.
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');
sql sql-server tsql datetime sql-server-2008-r2
sql sql-server tsql datetime sql-server-2008-r2
edited Nov 15 '18 at 9:05
Salman A
183k66340438
183k66340438
asked Nov 15 '18 at 7:53
kkrkkr
411
411
It's great you've posted sample data properly, but you should also post what you have tried so far. For more details, please read How to Ask.
– Zohar Peled
Nov 15 '18 at 8:09
Also, what if you have a value in theStartDate
that is the end of the month (like2018-01-31
)?
– Zohar Peled
Nov 15 '18 at 8:11
If the StartDate is 2018-01-31, Then the output should be StartDate - 2018-01-31 EndDate - 2018-01-31
– kkr
Nov 15 '18 at 8:17
This would be easily solved with a calendar table. Here is a (yet another) great article by Aaron Bertrand on how to create one.
– Zohar Peled
Nov 15 '18 at 8:46
add a comment |
It's great you've posted sample data properly, but you should also post what you have tried so far. For more details, please read How to Ask.
– Zohar Peled
Nov 15 '18 at 8:09
Also, what if you have a value in theStartDate
that is the end of the month (like2018-01-31
)?
– Zohar Peled
Nov 15 '18 at 8:11
If the StartDate is 2018-01-31, Then the output should be StartDate - 2018-01-31 EndDate - 2018-01-31
– kkr
Nov 15 '18 at 8:17
This would be easily solved with a calendar table. Here is a (yet another) great article by Aaron Bertrand on how to create one.
– Zohar Peled
Nov 15 '18 at 8:46
It's great you've posted sample data properly, but you should also post what you have tried so far. For more details, please read How to Ask.
– Zohar Peled
Nov 15 '18 at 8:09
It's great you've posted sample data properly, but you should also post what you have tried so far. For more details, please read How to Ask.
– Zohar Peled
Nov 15 '18 at 8:09
Also, what if you have a value in the
StartDate
that is the end of the month (like 2018-01-31
)?– Zohar Peled
Nov 15 '18 at 8:11
Also, what if you have a value in the
StartDate
that is the end of the month (like 2018-01-31
)?– Zohar Peled
Nov 15 '18 at 8:11
If the StartDate is 2018-01-31, Then the output should be StartDate - 2018-01-31 EndDate - 2018-01-31
– kkr
Nov 15 '18 at 8:17
If the StartDate is 2018-01-31, Then the output should be StartDate - 2018-01-31 EndDate - 2018-01-31
– kkr
Nov 15 '18 at 8:17
This would be easily solved with a calendar table. Here is a (yet another) great article by Aaron Bertrand on how to create one.
– Zohar Peled
Nov 15 '18 at 8:46
This would be easily solved with a calendar table. Here is a (yet another) great article by Aaron Bertrand on how to create one.
– Zohar Peled
Nov 15 '18 at 8:46
add a comment |
4 Answers
4
active
oldest
votes
You can use a recursive CTE. The basic idea is to start with the first date 2018-01-21
and build a list of all months' start and end date upto the last date 2018-12-31
. Then inner join with your data and clamp the dates if necessary.
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
If rCTE is not an option you can always JOIN with a table of numbers or table of dates (the idea above still applies).
2
EOMONTH
was introduced in 2012 version, and I think that IIF was too.
– Zohar Peled
Nov 15 '18 at 8:42
@Zohar changed to exclusive end dates but cannot verify if it runs on 2008.
– Salman A
Nov 15 '18 at 8:59
Neither can I.... all fiddle websites I know have only support for 2012 or higher, and I'm using 2016... I'm guessing that the OP can test, though :-)
– Zohar Peled
Nov 15 '18 at 9:03
Have a look at my attempt which does without the recursive cte
– George Joseph
Nov 15 '18 at 10:09
All of this is valid syntax and works on 2008
– Bridge
Nov 15 '18 at 12:09
add a comment |
You can Cross Apply with the Master..spt_values table to get a row for each month between StartDate and 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
What if there are dates across year, e.g. '2018-12-10', '2019-01-10'?
– dnoeth
Nov 15 '18 at 13:45
Good point. Updated my answer to cater for cross-year(s) date ranges
– wnutt
Nov 28 '18 at 11:30
add a comment |
The following should also work
- First i put startdates and enddates into a single column in the cte-block data.
- In the block som_eom, i create the start_of_month and end_of_month for all 12 months.
- I union steps 1 and 2 into curated_set
- I create curated_set which is ordered by the date column
- Finally i reject the unwanted records, in my filter clause not 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')
add a comment |
Start with the initial StartDate and calculate the end of month or simply use the EndDate if it's within the same month.
Use the newly calculated EndDate+1 as StartDate for recursion and repeat the calculation.
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
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53314669%2fsplitting-dates-into-intervals-using-start-date-and-end-date%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
You can use a recursive CTE. The basic idea is to start with the first date 2018-01-21
and build a list of all months' start and end date upto the last date 2018-12-31
. Then inner join with your data and clamp the dates if necessary.
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
If rCTE is not an option you can always JOIN with a table of numbers or table of dates (the idea above still applies).
2
EOMONTH
was introduced in 2012 version, and I think that IIF was too.
– Zohar Peled
Nov 15 '18 at 8:42
@Zohar changed to exclusive end dates but cannot verify if it runs on 2008.
– Salman A
Nov 15 '18 at 8:59
Neither can I.... all fiddle websites I know have only support for 2012 or higher, and I'm using 2016... I'm guessing that the OP can test, though :-)
– Zohar Peled
Nov 15 '18 at 9:03
Have a look at my attempt which does without the recursive cte
– George Joseph
Nov 15 '18 at 10:09
All of this is valid syntax and works on 2008
– Bridge
Nov 15 '18 at 12:09
add a comment |
You can use a recursive CTE. The basic idea is to start with the first date 2018-01-21
and build a list of all months' start and end date upto the last date 2018-12-31
. Then inner join with your data and clamp the dates if necessary.
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
If rCTE is not an option you can always JOIN with a table of numbers or table of dates (the idea above still applies).
2
EOMONTH
was introduced in 2012 version, and I think that IIF was too.
– Zohar Peled
Nov 15 '18 at 8:42
@Zohar changed to exclusive end dates but cannot verify if it runs on 2008.
– Salman A
Nov 15 '18 at 8:59
Neither can I.... all fiddle websites I know have only support for 2012 or higher, and I'm using 2016... I'm guessing that the OP can test, though :-)
– Zohar Peled
Nov 15 '18 at 9:03
Have a look at my attempt which does without the recursive cte
– George Joseph
Nov 15 '18 at 10:09
All of this is valid syntax and works on 2008
– Bridge
Nov 15 '18 at 12:09
add a comment |
You can use a recursive CTE. The basic idea is to start with the first date 2018-01-21
and build a list of all months' start and end date upto the last date 2018-12-31
. Then inner join with your data and clamp the dates if necessary.
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
If rCTE is not an option you can always JOIN with a table of numbers or table of dates (the idea above still applies).
You can use a recursive CTE. The basic idea is to start with the first date 2018-01-21
and build a list of all months' start and end date upto the last date 2018-12-31
. Then inner join with your data and clamp the dates if necessary.
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
If rCTE is not an option you can always JOIN with a table of numbers or table of dates (the idea above still applies).
edited Nov 15 '18 at 9:33
answered Nov 15 '18 at 8:34
Salman ASalman A
183k66340438
183k66340438
2
EOMONTH
was introduced in 2012 version, and I think that IIF was too.
– Zohar Peled
Nov 15 '18 at 8:42
@Zohar changed to exclusive end dates but cannot verify if it runs on 2008.
– Salman A
Nov 15 '18 at 8:59
Neither can I.... all fiddle websites I know have only support for 2012 or higher, and I'm using 2016... I'm guessing that the OP can test, though :-)
– Zohar Peled
Nov 15 '18 at 9:03
Have a look at my attempt which does without the recursive cte
– George Joseph
Nov 15 '18 at 10:09
All of this is valid syntax and works on 2008
– Bridge
Nov 15 '18 at 12:09
add a comment |
2
EOMONTH
was introduced in 2012 version, and I think that IIF was too.
– Zohar Peled
Nov 15 '18 at 8:42
@Zohar changed to exclusive end dates but cannot verify if it runs on 2008.
– Salman A
Nov 15 '18 at 8:59
Neither can I.... all fiddle websites I know have only support for 2012 or higher, and I'm using 2016... I'm guessing that the OP can test, though :-)
– Zohar Peled
Nov 15 '18 at 9:03
Have a look at my attempt which does without the recursive cte
– George Joseph
Nov 15 '18 at 10:09
All of this is valid syntax and works on 2008
– Bridge
Nov 15 '18 at 12:09
2
2
EOMONTH
was introduced in 2012 version, and I think that IIF was too.– Zohar Peled
Nov 15 '18 at 8:42
EOMONTH
was introduced in 2012 version, and I think that IIF was too.– Zohar Peled
Nov 15 '18 at 8:42
@Zohar changed to exclusive end dates but cannot verify if it runs on 2008.
– Salman A
Nov 15 '18 at 8:59
@Zohar changed to exclusive end dates but cannot verify if it runs on 2008.
– Salman A
Nov 15 '18 at 8:59
Neither can I.... all fiddle websites I know have only support for 2012 or higher, and I'm using 2016... I'm guessing that the OP can test, though :-)
– Zohar Peled
Nov 15 '18 at 9:03
Neither can I.... all fiddle websites I know have only support for 2012 or higher, and I'm using 2016... I'm guessing that the OP can test, though :-)
– Zohar Peled
Nov 15 '18 at 9:03
Have a look at my attempt which does without the recursive cte
– George Joseph
Nov 15 '18 at 10:09
Have a look at my attempt which does without the recursive cte
– George Joseph
Nov 15 '18 at 10:09
All of this is valid syntax and works on 2008
– Bridge
Nov 15 '18 at 12:09
All of this is valid syntax and works on 2008
– Bridge
Nov 15 '18 at 12:09
add a comment |
You can Cross Apply with the Master..spt_values table to get a row for each month between StartDate and 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
What if there are dates across year, e.g. '2018-12-10', '2019-01-10'?
– dnoeth
Nov 15 '18 at 13:45
Good point. Updated my answer to cater for cross-year(s) date ranges
– wnutt
Nov 28 '18 at 11:30
add a comment |
You can Cross Apply with the Master..spt_values table to get a row for each month between StartDate and 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
What if there are dates across year, e.g. '2018-12-10', '2019-01-10'?
– dnoeth
Nov 15 '18 at 13:45
Good point. Updated my answer to cater for cross-year(s) date ranges
– wnutt
Nov 28 '18 at 11:30
add a comment |
You can Cross Apply with the Master..spt_values table to get a row for each month between StartDate and 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
You can Cross Apply with the Master..spt_values table to get a row for each month between StartDate and 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
edited Nov 28 '18 at 11:26
answered Nov 15 '18 at 9:07
wnuttwnutt
38423
38423
What if there are dates across year, e.g. '2018-12-10', '2019-01-10'?
– dnoeth
Nov 15 '18 at 13:45
Good point. Updated my answer to cater for cross-year(s) date ranges
– wnutt
Nov 28 '18 at 11:30
add a comment |
What if there are dates across year, e.g. '2018-12-10', '2019-01-10'?
– dnoeth
Nov 15 '18 at 13:45
Good point. Updated my answer to cater for cross-year(s) date ranges
– wnutt
Nov 28 '18 at 11:30
What if there are dates across year, e.g. '2018-12-10', '2019-01-10'?
– dnoeth
Nov 15 '18 at 13:45
What if there are dates across year, e.g. '2018-12-10', '2019-01-10'?
– dnoeth
Nov 15 '18 at 13:45
Good point. Updated my answer to cater for cross-year(s) date ranges
– wnutt
Nov 28 '18 at 11:30
Good point. Updated my answer to cater for cross-year(s) date ranges
– wnutt
Nov 28 '18 at 11:30
add a comment |
The following should also work
- First i put startdates and enddates into a single column in the cte-block data.
- In the block som_eom, i create the start_of_month and end_of_month for all 12 months.
- I union steps 1 and 2 into curated_set
- I create curated_set which is ordered by the date column
- Finally i reject the unwanted records, in my filter clause not 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')
add a comment |
The following should also work
- First i put startdates and enddates into a single column in the cte-block data.
- In the block som_eom, i create the start_of_month and end_of_month for all 12 months.
- I union steps 1 and 2 into curated_set
- I create curated_set which is ordered by the date column
- Finally i reject the unwanted records, in my filter clause not 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')
add a comment |
The following should also work
- First i put startdates and enddates into a single column in the cte-block data.
- In the block som_eom, i create the start_of_month and end_of_month for all 12 months.
- I union steps 1 and 2 into curated_set
- I create curated_set which is ordered by the date column
- Finally i reject the unwanted records, in my filter clause not 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')
The following should also work
- First i put startdates and enddates into a single column in the cte-block data.
- In the block som_eom, i create the start_of_month and end_of_month for all 12 months.
- I union steps 1 and 2 into curated_set
- I create curated_set which is ordered by the date column
- Finally i reject the unwanted records, in my filter clause not 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')
answered Nov 15 '18 at 10:07
George JosephGeorge Joseph
1,59059
1,59059
add a comment |
add a comment |
Start with the initial StartDate and calculate the end of month or simply use the EndDate if it's within the same month.
Use the newly calculated EndDate+1 as StartDate for recursion and repeat the calculation.
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
add a comment |
Start with the initial StartDate and calculate the end of month or simply use the EndDate if it's within the same month.
Use the newly calculated EndDate+1 as StartDate for recursion and repeat the calculation.
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
add a comment |
Start with the initial StartDate and calculate the end of month or simply use the EndDate if it's within the same month.
Use the newly calculated EndDate+1 as StartDate for recursion and repeat the calculation.
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
Start with the initial StartDate and calculate the end of month or simply use the EndDate if it's within the same month.
Use the newly calculated EndDate+1 as StartDate for recursion and repeat the calculation.
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
answered Nov 15 '18 at 13:40
dnoethdnoeth
45.8k31839
45.8k31839
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53314669%2fsplitting-dates-into-intervals-using-start-date-and-end-date%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
It's great you've posted sample data properly, but you should also post what you have tried so far. For more details, please read How to Ask.
– Zohar Peled
Nov 15 '18 at 8:09
Also, what if you have a value in the
StartDate
that is the end of the month (like2018-01-31
)?– Zohar Peled
Nov 15 '18 at 8:11
If the StartDate is 2018-01-31, Then the output should be StartDate - 2018-01-31 EndDate - 2018-01-31
– kkr
Nov 15 '18 at 8:17
This would be easily solved with a calendar table. Here is a (yet another) great article by Aaron Bertrand on how to create one.
– Zohar Peled
Nov 15 '18 at 8:46