Home > Back-end >  Running subtraction but subtracting only if the amount is still above a limit
Running subtraction but subtracting only if the amount is still above a limit

Time:10-13

I have a table with the following structure, I want to subtract the TotalAmount to Amount but only if the remaining amount would still above the limit. The sorting is the sequence which amount should be tried first before moving on to the next data

Sort    Amount    Limit    TotalAmount
1       200       500      2000
3       500       500      2000
10      1000      500      2000
17      30        500      2000

Expected Result

Sort    Amount    Limit    TotalAmount    RemainingAmount
1       200       500      2000           1800
3       500       500      2000           1300  
10      1000      500      2000           1300  --This is still 1300 because 1300 - 1000 is 300
17      30        500      2000           1270

I've been trying window functions, but I don't know how to apply the logic of keeping the same amount with a condition.

SELECT Sort, Amount, Limit, TotalAmount, TotalAmount - SUM(Amount) OVER (ORDER BY Sort) as RemainingAmount

I also tried something with case and checking if i'm below the limit

CASE WHEN TotalAmount - SUM(Amount) OVER (ORDER BY Sort) < Limit THEN --readd the amount again?

CodePudding user response:

Declare a running variable @Amount to remember the amount deducted and do the calculation for each row selected.

SELECT Sort, Amount, `Limit`, TotalAmount, 
CASE WHEN TotalAmount - @Amount - Amount < `Limit` 
     THEN TotalAmount - @Amount ELSE TotalAmount - (@Amount := @Amount   Amount) 
     END AS RemainingAmount
FROM v 
JOIN ( SELECT @Amount := 0 ) f
ORDER BY Sort

CodePudding user response:

You can perform a self join to produce the baseline running totals, and then find the minimum running total for each given sort that, when subtracted by that sort's amount, does not fall below the limit:

with aggs(s, a) as (
   select v.sort, sum(coalesce(v1.amount, 0)) from vals v 
   left join vals v1 on v1.sort < v.sort 
   group by v.sort order by v.sort
),
r_m(s, a) as (
   select v.sort, min(v.totalamount - a1.a) from vals v 
   join aggs a1 on v.totalamount - a1.a >= v.lmt and v.sort >= a1.s
   group by v.sort
   order by v.sort
)
select v.*, case when r_m.a - v.amount >= v.lmt 
                 then r_m.a - v.amount else r_m.a end 
from vals v join r_m on v.sort = r_m.s

Output:

sort amount lmt totalamount remainingamount
1 200 500 2000 1800
3 500 500 2000 1300
10 1000 500 2000 1300
17 30 500 2000 1270
  • Related