Home > database >  For each row in a t-sql query resultset, get the startdate and enddate and extract months between, t
For each row in a t-sql query resultset, get the startdate and enddate and extract months between, t

Time:01-22

I have a problem that I am trying to solve with t-sql but I cant figure it out by myself.

I have a simple query:

select StartDate, EndDate
from ProductTable
where Site = 'X' and  Product_ID = '1'

The result can look like this (there can be one or more rows with start and end dates):

StartDate EndDate
2019-06-01 2019-09-30
2019-12-01 2020-04-30
2020-11-30 2020-12-31

What I want to do then is that for each row in this resultset, I want to create a list of months between the dates, on the format "yyyymm", and then union the result of these lists to one resultset.

So for the 3 rows in the first resultset the first step should give:

ROW 1: 201906, 201907, 201908, 201909

ROW 2: 201912, 202001, 202002, 202003, 202004

ROW 3: 202011, 202012

And the final expected result is then of course:

Months
201906
201907
201908
201909
201912
202001
202002
202003
202004
202011
202012

I have experimented a bit with CTEs and Cursors but I haven't real had any success yet.

Can someone help me out? :-)

CodePudding user response:

A recursive CTE works well to unfold date ranges.

;WITH RCTE_DATES AS (
   SELECT 
     DATEADD(month, -1, DATEADD(day, 1, EOMONTH(StartDate))) AS StartDate
   , DATEADD(month, -1, DATEADD(day, 1, EOMONTH(EndDate))) AS EndDate
   FROM ProductTable
   WHERE Site = 'X' AND Product_ID = '1'

   UNION ALL
   
   SELECT DATEADD(month, 1, StartDate), EndDate
   FROM RCTE_DATES
   WHERE StartDate < EndDate
)
, CTE_YEARMONTHS AS (
  SELECT DISTINCT
    YEAR(StartDate)*100 MONTH(StartDate) AS YearMonth
  FROM RCTE_DATES
) 
SELECT *
FROM CTE_YEARMONTHS
ORDER BY YearMonth;
| YearMonth |
| --------: |
|    201906 |
|    201907 |
|    201908 |
|    201909 |
|    201912 |
|    202001 |
|    202002 |
|    202003 |
|    202004 |
|    202011 |
|    202012 |

Test on db<>fiddle enter image description here

Yes, fnTally has fewer total reads, but higher estimates (30% higher, which could affect memory grants at scale), and a higher compile cost. Which of those is more important to you depends on your workload, the size of the product table, the skew in (a) matching rows and (b) max datediff, and your hardware.

Now, you can do this without a recursive CTE, a helper table, or a helper function, but it leads to higher reads:

;WITH m(m) AS 
(
  SELECT TOP (256) m = ROW_NUMBER() OVER (ORDER BY [object_id])-1
    FROM sys.all_objects ORDER BY [object_id]
)
SELECT CONVERT(char(6), DATEADD(MONTH, m.m, p.StartDate), 112)
  FROM dbo.ProductTable AS p
  INNER JOIN m ON m.m <= DATEDIFF(MONTH, p.StartDate, p.EndDate)
  WHERE Product_ID = '1' AND Site = 'X';

You can squeeze those read numbers down by reducing 256 in the TOP clause if you know your datediffs can be < 256 months, but it's hard to get faster than 0. Here are the comparison results:

enter image description here

And finally another good resource for number generator functions is this series from Itzik Ben-Gan (work backward and read all the comments).

  •  Tags:  
  • Related