Условие
Таблица #oper(date date, cnt int) хранит количество операций на каждую календарную дату. Нужно вывести на каждую дату количество операций с начала месяца по указанную дату включительно нарастающим итогом.
Тестовые данные
insert into #oper values
('2019-06-02', 1985), ('2019-06-03', 1577), ('2019-06-04', 1597),
('2019-06-05', 1468), ('2019-07-06', 82), ('2019-07-08', 1689),
('2019-07-09', 1556), ('2019-07-10', 1480), ('2019-07-11', 1405),
('2019-07-12', 1502);Решение
Подход
Классическая оконная функция с PARTITION BY по месяцу. Месяц считается границей рестарта суммы.
Реализация
SELECT
date,
cnt,
SUM(cnt) OVER (
PARTITION BY DATE_TRUNC('month', date)
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cnt_running
FROM #oper
ORDER BY date;В MS SQL Server вместо DATE_TRUNC используется DATEFROMPARTS(YEAR(date), MONTH(date), 1) или EOMONTH(date, -1).
Ожидаемый результат
| date | cnt | cnt_running |
|---|---|---|
| 2019-06-02 | 1985 | 1985 |
| 2019-06-03 | 1577 | 3562 |
| 2019-06-04 | 1597 | 5159 |
| 2019-06-05 | 1468 | 6627 |
| 2019-07-06 | 82 | 82 |
| 2019-07-08 | 1689 | 1771 |
| 2019-07-09 | 1556 | 3327 |
| 2019-07-10 | 1480 | 4807 |
| 2019-07-11 | 1405 | 6212 |
| 2019-07-12 | 1502 | 7714 |
Подводные камни
- Без
PARTITION BYпо месяцу сумма не сбрасывается на 1 июля — типичная ошибка. ORDER BYбезROWS BETWEENв большинстве СУБД эквивалентенRANGE UNBOUNDED PRECEDING AND CURRENT ROW. Если есть дубликаты дат —RANGEсуммирует все одинаковые даты в одну точку, что может дать неожиданный результат. Безопаснее явно указыватьROWS.- Пропущенные даты. Если в задании требуется заполнить пропуски нулями, нужен
cross joinсо справочником дат. - Часовой пояс. Если
date— этоtimestamp, придётся учитывать TZ при расчёте месяца.
Альтернатива
Через подзапрос-самоjoin:
SELECT
o1.date, o1.cnt,
(SELECT SUM(o2.cnt)
FROM #oper o2
WHERE YEAR(o2.date) = YEAR(o1.date)
AND MONTH(o2.date) = MONTH(o1.date)
AND o2.date <= o1.date) AS cnt_running
FROM #oper o1
ORDER BY o1.date;Менее эффективно (O(N²)) и менее читаемо.
Эталонный ответ
SUM(cnt) OVER (PARTITION BY DATE_TRUNC('month', date) ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) — нарастающий итог в пределах месяца.