Home > Mobile >  TSQL, break period by Month only for given year
TSQL, break period by Month only for given year

Time:11-30

I'm trying to break membership periods by month, but only for given year, in our example it 2022 Self inclusive code below works fine but it breaks all period, I only trying to process by month for 2022. Code below also holds definition for test data inside (#t temp table). I'm using #numbers table as a tally. I'm using SQL Server 2017

/*  
SELECT TOP 100  ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 AS n     
INTO #numbers     FROM   master.dbo.spt_values  --- Select top 100 * from #t numbers
  -----    data prep part
SELECT * INTO #t  FROM (    --   DROP TABLE IF EXISTS  #t
          SELECT 100 ID,  CAST('2022-03-02' AS DATE) eStart ,  CAST('2022-03-15' AS DATE) eEnd, '1 Same Month island' note , 100 Idx
    UNION SELECT 200,  '2022-01-11' ,   '2022-03-27', '2 Same Month Start 1', 2001
    UNION SELECT 200,  '2022-02-15' ,   '2022-5-15', '2 Same Month Start 2', 2002
    UNION SELECT 300,  '2021-05-08' ,   '2024-12-31', '3 Future eEnd ' ,3000
    UNION SELECT 440,  '2022-10-15' ,   '2023-10-10', '4 Future end 2' ,4400 
) b      --    SELECT * FROM #t
*/

select DISTINCT t.ID,
  CASE WHEN YEAR(DATEADD(MONTH, DATEDIFF(MONTH, 0, t.eStart)   n, 0)) < 2022 THEN eStart
       WHEN YEAR(DATEADD(MONTH, DATEDIFF(MONTH, 0, t.eStart)   n, 0)) > 2022 THEN '1/1/2023'  -- first day after 2022  
       ELSE  CASE WHEN n = 0  then t.eStart   ELSE   dateadd(month, datediff(month, 0, t.eStart)   n, 0)   END 
                                                                          END  eStart,  
  case when n = datediff(month, eStart, eEnd)   then t.eEnd
       else eomonth(dateadd(month, datediff(month, 0, t.eStart)   n, 0))  END eEnd 
    --   ,n, note ,eStart eStartOrig, eEnd eEndOrig
from   #t t
join #numbers n on n.n <= datediff(month, eStart, eEnd)
order by t.ID, eStart

enter image description here

CodePudding user response:

My suggested approach would be to build a "months" table for the desired range, join with your membership data, and keep pairs with overlapping date ranges. From there you can adjust the start and end dates to be the more restrictive between the actual mempership period and the selected month.

When comparing date ranges for overlap, a standard comparison is Start1 < End2 AND Start2 < End1 when dealing with exclusive end-dates or date/times. When dealing with inclusive end dates (as is the case here), the comparison becomes Start1 <= End2 AND Start2 <= End1

The resulting logic would then be something like:

-- Membership data #t as defined in original post

SELECT
    A.MonthStart,
    EOMONTH(A.MonthStart) AS MonthEnd
INTO #months
FROM (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12)) N(N)
CROSS APPLY (SELECT DATEFROMPARTS(2022, N.N, 1) AS MonthStart) A

SELECT * from #months

SELECT
    t.ID,
    CASE WHEN m.MonthStart >= t.eStart THEN m.MonthStart ELSE t.eStart END AS eStart,
    CASE WHEN m.MonthEnd <= t.eEnd THEN m.MonthEnd ELSE t.eEnd END AS eEnd,
    t.Idx
FROM #t t
JOIN #months m -- Overlapping months (inclusive dates)
    ON m.MonthStart <= t.eEnd 
    AND t.eStart <= m.MonthEnd 
ORDER BY t.ID, t.Idx, eStart -- Added t.Idx to better match desired results

See this db<>fiddle for a demo.

If you want to include prior and following membership date ranges, you can include extra rows in the #months table such as:

  • 1900-01-01 to 2022-12-31
  • 2024-01-01 to 12-31-9999

or specific full-year ranges if that is what is wanted.

CodePudding user response:

select DISTINCT t.ID,
  CASE WHEN YEAR(DATEADD(MONTH, DATEDIFF(MONTH, 0, t.eStart)   n, 0)) < 2022 THEN eStart
       WHEN YEAR(DATEADD(MONTH, DATEDIFF(MONTH, 0, t.eStart)   n, 0)) > 2022 THEN '1/1/2023'  -- first day after 2022  
       ELSE  CASE WHEN n = 0  then t.eStart   ELSE   dateadd(month, datediff(month, 0, t.eStart)   n, 0)   END 
                                                                          END  eStart,  
 CASE WHEN YEAR(         
              CASE WHEN n = DATEDIFF(MONTH, t.eStart, t.eEnd)  THEN t.eEnd  ELSE EOMONTH(DATEADD(MONTH, DATEDIFF(MONTH, 0, t.eStart)   n, 0))    END) < 2022
              THEN CASE WHEN t.eEnd < '1/1/2022'  THEN t.eEnd  ELSE DATEADD(d,-1,'1/1/2022') END
          WHEN YEAR(
              CASE WHEN n = DATEDIFF(MONTH, t.eStart, t.eEnd)  THEN t.eEnd  ELSE EOMONTH(DATEADD(MONTH, DATEDIFF(MONTH, 0, t.eStart)   n, 0))    END) > 2022
              THEN t.eEnd
          ELSE
              CASE WHEN n = DATEDIFF(MONTH, t.eStart, t.eEnd)  THEN t.eEnd  ELSE EOMONTH(DATEADD(MONTH, DATEDIFF(MONTH, 0, t.eStart)   n, 0))    END        
 END                                     eEnd  

from   #t t
join #numbers n on n.n <= datediff(month, eStart, eEnd)
  • Related