How can I use SQL to compute a cumulative sum over a column, so that the cumulative sum always stays within upper/lower bounds. Example with lower bound -2 and upper bound 10, s
You can (almost) always use a cursor
to implement whatever cumulative logic you have. The technique is quite routine so can be used to tackle a variety of problems easily once you get it.
One specific thing to note: Here I update the table in-place, so the [id]
column must be uniquely indexed.
(Tested on SQL Server 2017 latest linux docker image)
Test Dataset
use [testdb];
if OBJECT_ID('testdb..test') is not null
drop table testdb..test;
create table test (
[id] int,
[input] int,
);
insert into test (id, input)
values (1,5), (2,7), (3,-10), (4,-10), (5,5), (6,10);
Solution
/* A generic row-by-row cursor solution */
-- First of all, make [id] uniquely indexed to enable "where current of"
create unique index idx_id on test(id);
-- append answer columns
alter table test
add [cum_sum] int,
[bounded_cum_sum] int;
-- storage for each row
declare @id int,
@input int,
@cum_sum int,
@bounded_cum_sum int;
-- record accumulated values
declare @prev_cum_sum int = 0,
@prev_bounded_cum_sum int = 0;
-- open a cursor ordered by [id] and updatable for assigned columns
declare cur CURSOR local
for select [id], [input], [cum_sum], [bounded_cum_sum]
from test
order by id
for update of [cum_sum], [bounded_cum_sum];
open cur;
while 1=1 BEGIN
/* fetch next row and check termination condition */
fetch next from cur
into @id, @input, @cum_sum, @bounded_cum_sum;
if @@FETCH_STATUS <> 0
break;
/* program body */
-- main logic
set @cum_sum = @prev_cum_sum + @input;
set @bounded_cum_sum = @prev_bounded_cum_sum + @input;
if @bounded_cum_sum > 10 set @bounded_cum_sum=10
else if @bounded_cum_sum < -2 set @bounded_cum_sum=-2;
-- write the result back
update test
set [cum_sum] = @cum_sum,
[bounded_cum_sum] = @bounded_cum_sum
where current of cur;
-- setup for next row
set @prev_cum_sum = @cum_sum;
set @prev_bounded_cum_sum = @bounded_cum_sum;
END
-- cleanup
close cur;
deallocate cur;
-- show
select * from test;
Result
| | id | input | cum_sum | bounded_cum_sum |
|---|----|-------|---------|-----------------|
| 1 | 1 | 5 | 5 | 5 |
| 2 | 2 | 7 | 12 | 10 |
| 3 | 3 | -10 | 2 | 0 |
| 4 | 4 | -10 | -8 | -2 |
| 5 | 5 | 5 | -3 | 3 |
| 6 | 6 | 10 | 7 | 10 |