Вычисление кумулятивных итогов и бегущих сумм традиционно в SQL решалось через оконные функции (например, SUM() OVER(ORDER BY ...)), однако в ранних или упрощённых версиях СУБД доступны лишь подзапросы и группировки. Исторически архитекторы БД искали обходные пути до появления стандарта SQL:2003 с поддержкой оконных функций.
Проблема — при отсутствии оконных функций для каждой строки необходимо в явном виде вычислять сумму всех предыдущих значений, что приводит к O(N^2) вложенным запросам при достаточно больших выборках, если не применить хитрости.
Решение:
Обычно используют коррелированные подзапросы или временные таблицы с обновлением значений:
Пример кода:
-- Кумулятивная сумма коррелированным запросом SELECT t1.id, t1.amount, ( SELECT SUM(t2.amount) FROM transactions t2 WHERE t2.id <= t1.id ) AS running_total FROM transactions t1 ORDER BY t1.id; -- Через временную таблицу с ручным обновлением значения CREATE TEMPORARY TABLE temp_running (id INT, amount INT, running_total INT); -- Проходим по строкам средствами внешнего кода (например, pl/pgsql), последовательно добавляя сумму
Ключевые особенности:
Обеспечивает ли гарантированную сортировку ORDER BY в коррелированном подзапросе?
Нет — subquery не обязательно влияет на сам результат. Сортировка итоговой выборки всегда задается наружно в основном запросе: итог зависит только от фильтрации по WHERE.
Можно ли параллелить вычисление кумулятивной суммы в таком подходе?
Нет — последовательность очень важна, особенно при вычислениях в зависимости от предшествующих строк, из-за чего простое распараллеливание невозможно в обычном SQL.
Почему коррелированный подзапрос столь медленный при большом количестве строк?
Потому что для каждой строки заново вычисляется сумма по набору предыдущих строк. Это приводит к O(N^2) операциями. На образце 100 тыс. строк это уже может занять минуты или даже часы.
Аналитик посчитал дневную кумулятивную выручку по дате через коррелированный подзапрос, а в таблице периодически появлялись удалённые id (дыры). Итоговая сумма имела скачкообразные провалы и зависела не от даты, а от порядка id.
Плюсы:
Минусы:
Инженер вынес обработку кумулятивной суммы в скрипт ETL (Python/pandas), затем залил итоговые значения в отдельную таблицу, синхронизируя только новинки. Итоги всегда согласованы по дате, код работает быстро и с миллионами записей.
Плюсы:
Минусы: