Короткий ответ
Нужно использовать оконную функцию SUM(daily_amount) OVER (ORDER BY day), которая по умолчанию считает сумму от первой строки окна до текущей, формируя накопительный итог.
Как это работает подробнее
при наличии ORDER BY внутри OVER(...) без явного указания рамки (ROWS BETWEEN) большинство СУБД по умолчанию применяют рамку RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW — то есть суммируют все строки от начала до текущей по порядку сортировки.
Пример кода
question-38.sql
WITH daily AS (
SELECT order_date::date AS day, SUM(amount) AS daily_amount
FROM orders
GROUP BY order_date::date
)
SELECT day, daily_amount,
SUM(daily_amount) OVER (
ORDER BY day
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_amount
FROM daily
ORDER BY day;Что использовать на практике
- перед накопительной суммой по дням данные обычно сначала агрегируют до одной строки на день (через
GROUP BYв CTE), а затем оконную функцию применяют уже к агрегированным дневным суммам — иначе при RANGE-рамке строки с одинаковой датой «сольются» в одну точку накопления, что может быть неожиданным. Последняя строкаcumulative_amountдолжна совпадать с общей суммой за весь период. Проверка: сравнить это значение сSELECT SUM(amount) FROM orders.
Подводные камни
- не агрегируют данные до одного дня предварительно и получают кумулятивную сумму на уровне отдельных транзакций, где все транзакции одного дня (из-за RANGE по умолчанию) получают одинаковое накопленное значение — равное сумме за весь день, а не частичной сумме внутри дня; забывают, что без
ORDER BYвOVER()сумма считается по всему окну сразу, то есть это обычныйSUM, а не накопительный.