← все задачи

SQL · задача 5 из 10

Накопительный итог по дням

Средний 15 минут SUM OVERрамки окнагруппировка по дате

Условие

Таблица payments (id, paid_at, amount). Посчитайте выручку по дням и нарастающий итог с начала месяца.

Что требуется

  • Одна строка на день
  • Колонка с суммой за день и колонка с накопленным итогом
  • Итог считается в хронологическом порядке

Пример

day         revenue   running_total
2026-09-01     1000            1000
2026-09-02      500            1500
2026-09-03      700            2200

Сначала уточните

Вопросы до кода — половина оценки. Молча начать печатать хуже, чем задать два вопроса.

  • Часовой пояс: сутки по UTC или по местному времени? Граница дня сдвигается
  • Нужны ли дни без платежей — их в таблице просто нет
  • Накопление с начала месяца или за всё время?
Показать решение Скрыть решение

Решение

SELECT
    day,
    revenue,
    SUM(revenue) OVER (ORDER BY day ROWS UNBOUNDED PRECEDING) AS running_total
FROM (
    SELECT
        date_trunc('day', paid_at) AS day,
        SUM(amount) AS revenue
    FROM payments
    WHERE paid_at >= date_trunc('month', CURRENT_DATE)
    GROUP BY 1
) daily
ORDER BY day;

Почему так

Почему два уровня: сначала группировка, потом окно

  • Сначала нужно схлопнуть платежи в дни, и только потом накапливать по дням
  • Окно поверх сырых платежей дало бы итог по каждой транзакции, а не по дню
  • Порядок «агрегация → окно» — общий шаблон для любых отчётов с накоплением

Зачем ROWS UNBOUNDED PRECEDING

  • По умолчанию для окна с ORDER BY рамка — RANGE от начала до текущей строки включая равные ей
  • На уникальных днях результат тот же, но при дубликатах ключа RANGE схлопнет их в одно значение
  • Явная рамка ROWS убирает неоднозначность — и знание этой детали отличает уверенное владение окнами

Почему WHERE, а не фильтр в окне

  • Условие по дате в WHERE отсекает лишние строки до всей работы — это самый дешёвый фильтр
  • Если по paid_at есть индекс, планировщик возьмёт только нужный диапазон
  • Фильтрация после агрегации заставила бы читать всю таблицу платежей за все годы

Что спросят дальше

  • Спросят про пропущенные дни: нужен generate_series и LEFT JOIN, иначе в графике будут дыры
  • Спросят про скользящее среднее за 7 дней: ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  • Спросят про часовые пояса: date_trunc по UTC и по Europe/Moscow дадут разные дни — для выручки это реальные деньги в отчёте

Следующая задача

Дни без продаж — Найти то, чего нет в таблице: строки нужно сначала откуда-то взять.