sqlsql-servercommon-table-expressionrecursive-query

Recursive CTE to insert an entry for every day


Consider the following:

IF OBJECT_ID('tempdb..#TetstData') IS NOT NULL
    DROP TABLE #TetstData
 
CREATE TABLE #TetstData
(
    Id INT NOT NULL, 
    PurchaseDate DATE NOT NULL,
    DepreciationRate DECIMAL(18,9)
)

INSERT INTO #TetstData VALUES(1, '2019/01/01', 0.123456789)
INSERT INTO #TetstData VALUES(2, '2021/01/01', 0.987654321)

;WITH cte_daily AS
(
    SELECT
        ROW_NUMBER() OVER (PARTITION BY Id ORDER BY t.PurchaseDate ASC) AS rowNumber,
        t.Id, 
        t.PurchaseDate, 
        t.DepreciationRate,
        0 AS PreviousDepreciationRate
    FROM
        #TetstData t

    UNION ALL

    SELECT
        ROW_NUMBER() OVER (PARTITION BY t.Id ORDER BY t.PurchaseDate ASC) AS rowNumber,
        t.Id, 
        DATEADD(DAY, 1, t.PurchaseDate) as PurchaseDate, 
        t.DepreciationRate, 
        0 AS PreviousDepreciationRate --access previous iteration value, if possible
    FROM
        #TetstData t 
    INNER JOIN
        cte_daily c ON t.Id = c.Id
    WHERE
        t.PurchaseDate < GETDATE()
)
SELECT * 
FROM cte_daily
OPTION (maxrecursion 0)

I need to create an entry, for everyday, from the PurchaseDate until the current date. I have tried with a recursive CTE, but unfortunately, it does not give the desired results.

I am not sure, if this is even possible with a CTE. Any help would be appreciated


Solution

  • The main error that makes your recursive CTE loop indefinitely if that you compare to t instead of c.
    In your recursive part SELECT … DATEADD(DAY, 1, t.PurchaseDate) … FROM #TetstData t … WHERE t.PurchaseDate < GETDATE(),
    you're basing your stop condition of the loop onto t which does not change (t.PurchaseDate is always 2019/01/01, thus it will never be >= GETDATE() which would stop the loop).
    In the same way, the increment being based on t.PurchaseDate, will always be 2019/01/02 on the next iteration of the loop for this Id.

    Thus make sure your loop condition, and the recursively built row, are based on the recursive CTE itself, not on the "static" part:

    DATEADD(DAY, 1, c.PurchaseDate), … WHERE c.PurchaseDate < GETDATE()
    

    To prevent this kind of error, I would recommend to never use OPTION (maxrecursion 0) until your loop is working:
    you could have tested with a date less than 100 days before today, or (if running on a real dataset with 2021) having added a condition to filter to id 2 with a maxrecursion 2000 (3 years is approximately 1000 days, so 2000 days is reasonable for a quick test of 2021).

    By simply applying these rules you'll be able to see you loop run (see it in a test fiddle),
    and then work on your query to refine it, use DepreciationRate, and so on.

    You'll probably converge to a query such as the one Chris Maurer gave in his answer, so I won't replicate it here.

    However two points that maybe need an explanation:

    I won't talk about PreviousDepreciationRate, which is quite unclear in your question and that you should explain by giving some example rows with the value you expect in it.