Skip to content

Loan repayment schedule 💰

Tip

Solution to the following problem:

Result Set

Regardless of the database, the result set should look like:

loan_id repayment_number repayment_date interest principal total balance
1 1 2024-02-01 1600.00 12682.06 14282.06 67317.94
1 2 2024-03-01 1346.36 12935.70 14282.06 54382.24
1 3 2024-04-01 1087.64 13194.42 14282.06 41187.82
1 4 2024-05-01 823.76 13458.30 14282.06 27729.52
1 5 2024-06-01 554.59 13727.47 14282.06 14002.05
1 6 2024-07-01 280.04 14002.05 14282.09 0.00
2 1 2024-02-02 1125.00 5751.00 6876.00 69249.00
2 2 2024-03-02 1038.74 5837.26 6876.00 63411.74
2 3 2024-04-02 951.18 5924.82 6876.00 57486.92
2 4 2024-05-02 862.30 6013.70 6876.00 51473.22
2 5 2024-06-02 772.10 6103.90 6876.00 45369.32
2 6 2024-07-02 680.54 6195.46 6876.00 39173.86
2 7 2024-08-02 587.61 6288.39 6876.00 32885.47
2 8 2024-09-02 493.28 6382.72 6876.00 26502.75
2 9 2024-10-02 397.54 6478.46 6876.00 20024.29
2 10 2024-11-02 300.36 6575.64 6876.00 13448.65
2 11 2024-12-02 201.73 6674.27 6876.00 6774.38
2 12 2025-01-02 101.62 6774.38 6876.00 0.00
3 1 2024-02-03 1000.00 3707.35 4707.35 96292.65
3 2 2024-03-03 962.93 3744.42 4707.35 92548.23
3 3 2024-04-03 925.48 3781.87 4707.35 88766.36
3 4 2024-05-03 887.66 3819.69 4707.35 84946.67
3 5 2024-06-03 849.47 3857.88 4707.35 81088.79
3 6 2024-07-03 810.89 3896.46 4707.35 77192.33
3 7 2024-08-03 771.92 3935.43 4707.35 73256.90
3 8 2024-09-03 732.57 3974.78 4707.35 69282.12
3 9 2024-10-03 692.82 4014.53 4707.35 65267.59
3 10 2024-11-03 652.68 4054.67 4707.35 61212.92
3 11 2024-12-03 612.13 4095.22 4707.35 57117.70
3 12 2025-01-03 571.18 4136.17 4707.35 52981.53
3 13 2025-02-03 529.82 4177.53 4707.35 48804.00
3 14 2025-03-03 488.04 4219.31 4707.35 44584.69
3 15 2025-04-03 445.85 4261.50 4707.35 40323.19
3 16 2025-05-03 403.23 4304.12 4707.35 36019.07
3 17 2025-06-03 360.19 4347.16 4707.35 31671.91
3 18 2025-07-03 316.72 4390.63 4707.35 27281.28
3 19 2025-08-03 272.81 4434.54 4707.35 22846.74
3 20 2025-09-03 228.47 4478.88 4707.35 18367.86
3 21 2025-10-03 183.68 4523.67 4707.35 13844.19
3 22 2025-11-03 138.44 4568.91 4707.35 9275.28
3 23 2025-12-03 92.75 4614.60 4707.35 4660.68
3 24 2026-01-03 46.61 4660.68 4707.29 0.00
Expand for the DDL
solution(loan_id, repayment_number, repayment_date, interest, principal, total, balance) as (
    values
        (1,  1, '2024-02-01'::date, 1600.00, 12682.06, 14282.06, 67317.94),
        (1,  2, '2024-03-01'::date, 1346.36, 12935.70, 14282.06, 54382.24),
        (1,  3, '2024-04-01'::date, 1087.64, 13194.42, 14282.06, 41187.82),
        (1,  4, '2024-05-01'::date,  823.76, 13458.30, 14282.06, 27729.52),
        (1,  5, '2024-06-01'::date,  554.59, 13727.47, 14282.06, 14002.05),
        (1,  6, '2024-07-01'::date,  280.04, 14002.05, 14282.09,     0.00),
        (2,  1, '2024-02-02'::date, 1125.00,  5751.00,  6876.00, 69249.00),
        (2,  2, '2024-03-02'::date, 1038.74,  5837.26,  6876.00, 63411.74),
        (2,  3, '2024-04-02'::date,  951.18,  5924.82,  6876.00, 57486.92),
        (2,  4, '2024-05-02'::date,  862.30,  6013.70,  6876.00, 51473.22),
        (2,  5, '2024-06-02'::date,  772.10,  6103.90,  6876.00, 45369.32),
        (2,  6, '2024-07-02'::date,  680.54,  6195.46,  6876.00, 39173.86),
        (2,  7, '2024-08-02'::date,  587.61,  6288.39,  6876.00, 32885.47),
        (2,  8, '2024-09-02'::date,  493.28,  6382.72,  6876.00, 26502.75),
        (2,  9, '2024-10-02'::date,  397.54,  6478.46,  6876.00, 20024.29),
        (2, 10, '2024-11-02'::date,  300.36,  6575.64,  6876.00, 13448.65),
        (2, 11, '2024-12-02'::date,  201.73,  6674.27,  6876.00,  6774.38),
        (2, 12, '2025-01-02'::date,  101.62,  6774.38,  6876.00,     0.00),
        (3,  1, '2024-02-03'::date, 1000.00,  3707.35,  4707.35, 96292.65),
        (3,  2, '2024-03-03'::date,  962.93,  3744.42,  4707.35, 92548.23),
        (3,  3, '2024-04-03'::date,  925.48,  3781.87,  4707.35, 88766.36),
        (3,  4, '2024-05-03'::date,  887.66,  3819.69,  4707.35, 84946.67),
        (3,  5, '2024-06-03'::date,  849.47,  3857.88,  4707.35, 81088.79),
        (3,  6, '2024-07-03'::date,  810.89,  3896.46,  4707.35, 77192.33),
        (3,  7, '2024-08-03'::date,  771.92,  3935.43,  4707.35, 73256.90),
        (3,  8, '2024-09-03'::date,  732.57,  3974.78,  4707.35, 69282.12),
        (3,  9, '2024-10-03'::date,  692.82,  4014.53,  4707.35, 65267.59),
        (3, 10, '2024-11-03'::date,  652.68,  4054.67,  4707.35, 61212.92),
        (3, 11, '2024-12-03'::date,  612.13,  4095.22,  4707.35, 57117.70),
        (3, 12, '2025-01-03'::date,  571.18,  4136.17,  4707.35, 52981.53),
        (3, 13, '2025-02-03'::date,  529.82,  4177.53,  4707.35, 48804.00),
        (3, 14, '2025-03-03'::date,  488.04,  4219.31,  4707.35, 44584.69),
        (3, 15, '2025-04-03'::date,  445.85,  4261.50,  4707.35, 40323.19),
        (3, 16, '2025-05-03'::date,  403.23,  4304.12,  4707.35, 36019.07),
        (3, 17, '2025-06-03'::date,  360.19,  4347.16,  4707.35, 31671.91),
        (3, 18, '2025-07-03'::date,  316.72,  4390.63,  4707.35, 27281.28),
        (3, 19, '2025-08-03'::date,  272.81,  4434.54,  4707.35, 22846.74),
        (3, 20, '2025-09-03'::date,  228.47,  4478.88,  4707.35, 18367.86),
        (3, 21, '2025-10-03'::date,  183.68,  4523.67,  4707.35, 13844.19),
        (3, 22, '2025-11-03'::date,  138.44,  4568.91,  4707.35,  9275.28),
        (3, 23, '2025-12-03'::date,   92.75,  4614.60,  4707.35,  4660.68),
        (3, 24, '2026-01-03'::date,   46.61,  4660.68,  4707.29,     0.00)
)

Solution

Some SQL solutions per database are provided below.

DuckDB

with recursive

monthly_repayment_value as (
    select
        loan_id,
        power(1 + interest_rate, repayments) as amortised_rate,
        round(
            (loan_value * interest_rate * amortised_rate) / (amortised_rate - 1),
            2
        ) as monthly_repayment
    from loans
),

schedule as (
        select
            /* loan details */
            loans.loan_id,
            loans.interest_rate,
            loans.repayments,
            monthly_repayment_value.monthly_repayment,

            /* repayment details */
            0 as repayment_number,
            loans.start_date as repayment_date,
            0::decimal(10, 2) as starting_balance,
            0::decimal(10, 2) as interest,
            0::decimal(10, 2) as principal,
            0::decimal(10, 2) as total,
            loans.loan_value as remaining_balance
        from loans
            inner join monthly_repayment_value
                using (loan_id)
    union all
        select
            loan_id,
            interest_rate,
            repayments,
            monthly_repayment,

            repayment_number + 1,
            repayment_date + interval '1 month',
            remaining_balance,
            round(remaining_balance * interest_rate, 2) as interest_,
            monthly_repayment - interest_ as principal_,
            monthly_repayment,
            remaining_balance - principal_
        from schedule
        where repayment_number < repayments
)

select
    loan_id,
    repayment_number,
    repayment_date,
    interest,

    /* adjust the final repayment with the rounding error */
    if(repayment_number = repayments, starting_balance, principal) as principal,
    if(repayment_number = repayments, starting_balance + interest, total) as total,
    if(repayment_number = repayments, 0, remaining_balance) as balance
from schedule
where repayment_number > 0
order by
    loan_id,
    repayment_number