Splitting dates into intervals using Start Date and End Date










8















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');









share|improve this question
























  • 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












  • 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















8















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');









share|improve this question
























  • 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












  • 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













8












8








8


3






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');









share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 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












  • 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












  • 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












  • 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












4 Answers
4






active

oldest

votes


















2














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).






share|improve this answer




















  • 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


















1














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





share|improve this answer

























  • 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


















0















The following should also work



  1. First i put startdates and enddates into a single column in the cte-block data.

  2. In the block som_eom, i create the start_of_month and end_of_month for all 12 months.

  3. I union steps 1 and 2 into curated_set

  4. I create curated_set which is ordered by the date column

  5. 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')





share|improve this answer






























    0














    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





    share|improve this answer






















      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
      );



      );













      draft saved

      draft discarded


















      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









      2














      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).






      share|improve this answer




















      • 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














      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).






      share|improve this answer




















      • 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








      2







      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).






      share|improve this answer















      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).







      share|improve this answer














      share|improve this answer



      share|improve this answer








      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












      • 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













      1














      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





      share|improve this answer

























      • 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















      1














      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





      share|improve this answer

























      • 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













      1












      1








      1







      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





      share|improve this answer















      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






      share|improve this answer














      share|improve this answer



      share|improve this answer








      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

















      • 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











      0















      The following should also work



      1. First i put startdates and enddates into a single column in the cte-block data.

      2. In the block som_eom, i create the start_of_month and end_of_month for all 12 months.

      3. I union steps 1 and 2 into curated_set

      4. I create curated_set which is ordered by the date column

      5. 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')





      share|improve this answer



























        0















        The following should also work



        1. First i put startdates and enddates into a single column in the cte-block data.

        2. In the block som_eom, i create the start_of_month and end_of_month for all 12 months.

        3. I union steps 1 and 2 into curated_set

        4. I create curated_set which is ordered by the date column

        5. 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')





        share|improve this answer

























          0












          0








          0








          The following should also work



          1. First i put startdates and enddates into a single column in the cte-block data.

          2. In the block som_eom, i create the start_of_month and end_of_month for all 12 months.

          3. I union steps 1 and 2 into curated_set

          4. I create curated_set which is ordered by the date column

          5. 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')





          share|improve this answer














          The following should also work



          1. First i put startdates and enddates into a single column in the cte-block data.

          2. In the block som_eom, i create the start_of_month and end_of_month for all 12 months.

          3. I union steps 1 and 2 into curated_set

          4. I create curated_set which is ordered by the date column

          5. 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')






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 15 '18 at 10:07









          George JosephGeorge Joseph

          1,59059




          1,59059





















              0














              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





              share|improve this answer



























                0














                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





                share|improve this answer

























                  0












                  0








                  0







                  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





                  share|improve this answer













                  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






                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 15 '18 at 13:40









                  dnoethdnoeth

                  45.8k31839




                  45.8k31839



























                      draft saved

                      draft discarded
















































                      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.




                      draft saved


                      draft discarded














                      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





















































                      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







                      這個網誌中的熱門文章

                      How to read a connectionString WITH PROVIDER in .NET Core?

                      Node.js Script on GitHub Pages or Amazon S3

                      Museum of Modern and Contemporary Art of Trento and Rovereto