Search code examples
t-sqlssmsset-based

Set-based approach to distributing a value SSMS TSQL


I have a table that includes a total debt and an annual repayment rate, payment start date and a calculated value for the first years prorata payment. I need to create/amend the table so that the payment schedule for each ID is set out in rows, with the first years pro-rata payment happening at the start date, and then subsequent rows/years payments being made at the payment rate until the debt is totally paid.

For example for the first ID, The debt is 100 to be repaid at 24 annually. The first payment (2) is to be made in 2023. The next payment and all other payments save any residual will be 24 (the full repayment rate) until all debt is accounted for.

I've tried various approaches, most of them trying to use some kind of iteration or loop which i was crucified for. I think windows functions might be something but i'm struggling to understand how to do that in this context. I'm definitely not getting my head around how to begin to approach this as a set-based problem - I tend to think procedurally which doesn't seem like the correct approach.

Any and all help appreciated.

Equally if a set-based approach isn't the way to go for this problem- help with an alternative would be great.

CREATE TABLE #full (
ID NVARCHAR(10),
StartDate DATE,
[Total Debt] INT,
RepaymentRate INT,
[Yr 1 partial Rate] INT,
Amount INT);

INSERT INTO #full
VALUES
(1, '2023-01-01',   100,    24, 2,   0),
(2, '2024-01-01',   100,    11, 5,   0),
(3, '2025-01-01',   100,    32, 7,   0),
(4, '2026-01-01',   100,    9,  2,   0),
(5, '2027-01-01',   100,    5,  24,  0)

Expected output:

:ID: :Start: :Total Debt: :Rate: :Yr1Rate: :Amount: : PayYear :
1 01/01/2023 100 24 2 2 01/01/2023
1 01/01/2023 100 24 2 24 01/01/2024
1 01/01/2023 100 24 2 24 01/01/2025
1 01/01/2023 100 24 2 24 01/01/2026
1 01/01/2023 100 24 2 24 01/01/2027
1 01/01/2023 100 24 2 2 01/01/2028
2 01/01/2024 100 11 5 5 01/01/2024

Solution

  • I use recursive CTE in the end:

    --Get first year's prorata delivery sorted first
    WITH RecurCTE AS (
        SELECT
            ID, StartDate, [Total Debt], RepaymentRate,
            [Yr 1 partial Rate] AS Amount, --Provide the pro_rata value as the amount
            [Total Debt] - [Yr 1 partial Rate] AS RemainingDebt --Calculate a remaining figure to be delivered after a row's delivery
        FROM #full
        --Rescursive element - adding a row per each years projected delivery
        UNION ALL
           SELECT
           c.ID,
            DATEADD(YEAR, 1, c.StartDate) AS StartDate,
            c.[Total Debt],
            c.RepaymentRate,
            -- where the amount still to be accounted for minus the rate is more than zero
            --(e.g. the full rate should be used for this row and not any residual figure) Amount = full rate
            --Otherwise if the remainder is less than the full rate, Amount = remaining value
            CASE
                WHEN c.RemainingDebt - c.RepaymentRate > 0 THEN c.RepaymentRate
                ELSE c.RemainingDebt
            END AS Amount,
    
            --Recaulcate remainder to reflect allocation made above (note this is current remainder minus same logic above for latest allocation)
            c.RemainingDebt - CASE
                WHEN c.RemainingDebt - c.RepaymentRate > 0 THEN c.RepaymentRate
                ELSE c.RemainingDebt
            END AS RemainingDebt
        FROM
            RecurCTE c
        WHERE --keep going until remaining left to allocate to a year is zero
            c.RemainingDebt > 0
    )
    
    -- Selecting the result from the CTE with a reasonable recursion limit
    SELECT
        ID,
        StartDate,
        RepaymentRate,
        Amount
    FROM
        RecurCTE
    ORDER BY
        ID,
        StartDate
    OPTION (MAXRECURSION 100);