Cumulative sum by day with non-negative floor with PostgreSQL
2
votes
1
answer
1279
views
Using PostgreSQL 12, I need to calculate the cumulative sum by day while filling in missing days. I *also* need to set a minimum value of
0
if any cumulative sum goes negative.
### Background
There are two excellent answers for [cumulative sum with missing days](https://stackoverflow.com/questions/32490946/cumulative-sum-of-values-by-month-filling-in-for-missing-months?noredirect=1&lq=1) and [non-negative floor values](https://dba.stackexchange.com/questions/179367/set-non-negative-floor-for-rolling-sum-in-postgresql) . I need to combine these two solutions.
Take this table and data as an example (note, this is also contained in [this db fiddle](https://dbfiddle.uk/?rdbms=postgres_12&fiddle=e84c62c5b4e95ebdd4c027b5f241a21f))
CREATE TABLE inventory_test (
"id" serial,
"account_id" bigint NOT NULL,
"is_active" boolean NOT NULL DEFAULT 'true',
"object_timestamp" bigint NOT NULL,
"amount" numeric(12, 1) NOT NULL,
"object_id" bigint NOT NULL,
PRIMARY KEY ("id")
);
*Note that object_timestamp
is epoch time in milliseconds
SELECT id, to_timestamp((object_timestamp + 0 )/1000), amount
FROM inventory_test
ORDER BY object_timestamp;
| id | to_timestamp | amount |
|----|--------------|--------|
| 1| 2022-04-08 15:13:30+01| 10000.0 |
| 2| 2022-04-09 15:13:30+01| -2000.0 |
| 3| 2022-04-09 15:13:31+01| -1000.0 |
| 4| 2022-04-10 15:13:30+01| -2000.0 |
| 5| 2022-04-11 15:13:30+01| -3000.0 |
| 6| 2022-04-12 15:13:30+01| -2500.0 |
| 7| 2022-04-15 15:13:30+01| -2500.0 |
| 8| 2022-04-16 15:13:30+01| -3000.0 |
| 9| 2022-04-17 15:13:30+01| 1000.0 |
I can generate the daily totals accounting for missing days with:
WITH cte as (
SELECT
date_trunc('day', to_timestamp((object_timestamp)/1000)) as day,
sum(amount) as day_sum
FROM inventory_test
WHERE object_id = 1 AND is_active = true
GROUP by 1
)
SELECT
day,
sum(c.day_sum) OVER (ORDER BY day) AS running_sum
FROM
(SELECT min(day) AS min_day FROM cte) init,
generate_series(init.min_day, now(), interval '1 day') day
LEFT JOIN cte c USING (day)
ORDER BY day;
|day | running_sum |
|-----------|---------------|
| 2022-04-08 00:00:00+00| 10000 |
| 2022-04-09 00:00:00+00| 7000 |
| 2022-04-10 00:00:00+00| 5000 |
| 2022-04-11 00:00:00+00| 2000 |
| 2022-04-12 00:00:00+00| -500 |
| 2022-04-13 00:00:00+00| -500 |
| 2022-04-14 00:00:00+00| -500 |
| 2022-04-15 00:00:00+00| -3000 |
| 2022-04-16 00:00:00+00| -6000 |
| 2022-04-17 00:00:00+00| -5000 |
### DB fiddle
You can see this in [this DB fiddle](https://dbfiddle.uk/?rdbms=postgres_12&fiddle=e84c62c5b4e95ebdd4c027b5f241a21f)
### Desired results
What I _want_ the results to look like are non-negative, where any positive changes are applied to the adjusted running total which should be MAX(0, running_sum)
. I'd prefer to solve this in a SQL statement rather than a custom function.
Desired output:
|day | running_sum |
|-----------|---------------|
| 2022-04-08 00:00:00+00| 10000 |
| 2022-04-09 00:00:00+00| 7000 |
| 2022-04-10 00:00:00+00| 5000 |
| 2022-04-11 00:00:00+00| 2000 |
| 2022-04-12 00:00:00+00| 0 |
| 2022-04-13 00:00:00+00| 0|
| 2022-04-14 00:00:00+00| 0 |
| 2022-04-15 00:00:00+00| 0 |
| 2022-04-16 00:00:00+00| 0 |
| 2022-04-17 00:00:00+00| 1000 |
Asked by brianz
(121 rep)
Apr 18, 2022, 03:34 AM
Last activity: Apr 21, 2022, 02:35 PM
Last activity: Apr 21, 2022, 02:35 PM