For a ... ran once ... calculation you have at least these options:
A numbers table, generated using a JSON-based approach and an APPLY
operator. Starting from SQL Server 2016 you may generate a serie of numbers using OPENJSON()
. The idea is to build a JSON array with the correct number of items ([1, 1, 1, ..., 1]
) and parse this array with OPENJSON()
and default schema. The result is a table with columns key
, value
, type
and the key
column returns the 0-based index of each item in the array.
A numbers table, generated with GENERATE_SERIES()
and an APPLY
operator. Starting from SQL Server 2022 you may use GENERATE_SERIES()
to build the necessary serie of numbers.
A recursive query.
Sample data:
SELECT *
INTO Data
FROM (VALUES
(600097, CONVERT(date, '20230130'), CONVERT(date, '20230202')),
(602025, CONVERT(date, '20230902'), CONVERT(date, '20230904'))
) v (ID, STARTDATE, ENDDATE)
Statement using OPENJSON()
:
SELECT
d.ID,
DATEADD(day, CONVERT(int, j.[key]), d.STARTDATE) AS STARTDATE,
DATEADD(day, CONVERT(int, j.[key]), d.STARTDATE) AS ENDDATE
FROM Data d
CROSS APPLY OPENJSON('[1' + REPLICATE(',1', DATEDIFF(day, d.STARTDATE, d.ENDDATE)) + ']') j
Statement using GENERATE_SERIES()
:
SELECT
d.ID,
DATEADD(day, s.[value], d.STARTDATE) AS STARTDATE,
DATEADD(day, s.[value], d.STARTDATE) AS ENDDATE
FROM Data d
CROSS APPLY GENERATE_SERIES(0, DATEDIFF(day, d.STARTDATE, d.ENDDATE)) s
Recursive query:
; WITH rCTE AS (
SELECT d.ID, d.STARTDATE, d.STARTDATE AS ENDDATE
FROM Data d
UNION ALL
SELECT r.ID, DATEADD(day, 1, r.STARTDATE), DATEADD(day, 1, r.STARTDATE)
FROM Data d
INNER JOIN rCTE r ON (r.ID = d.ID) AND (r.ENDDATE < d.ENDDATE)
)
SELECT *
FROM rCTE
ORDER BY ID, STARTDATE
Result:
ID |
STARTDATE |
ENDDATE |
600097 |
2023-01-30 |
2023-01-30 |
600097 |
2023-01-31 |
2023-01-31 |
600097 |
2023-02-01 |
2023-02-01 |
600097 |
2023-02-02 |
2023-02-02 |
602025 |
2023-09-02 |
2023-09-02 |
602025 |
2023-09-03 |
2023-09-03 |
602025 |
2023-09-04 |
2023-09-04 |