Скользящее среднее в SQL: rolling-окна и тренды

0 задач Лёгкие, средние, сложные С эталонными решениями Решение в браузере
Скользящее среднее просят почти на каждом собесе, где есть метрики и графики. Формулировка простая: «Сглади дневную выручку 7-дневным средним» или «Покажи rolling sum заказов за последние 30 дней на каждую дату». За этим стоит проверка, понимаете ли вы оконную рамку — тот самый ROWS BETWEEN ... AND CURRENT ROW, который большинство помнит смутно. AVG(revenue) OVER (ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) — вот и весь rolling за 7 дней, без единого JOIN. Но дьявол в рамках: ROWS считает строки, RANGE считает по значению, и если в данных есть пропущенные даты, результат разъедется. На собесе в продуктовых командах это ещё и про чтение дашбордов: сырая дневная метрика шумит, а 7- или 28-дневное скользящее показывает настоящий тренд и убирает недельную сезонность. Кто умеет за 30 секунд написать корректное rolling-окно, тот обычно и с временными рядами дружит, и графики читает не на глаз.
Начать решать задачи →

Все задачи на «moving-average» (0)

FAQ: частые вопросы про moving-average

Как написать 7-дневное скользящее среднее в PostgreSQL?

Через оконную функцию с рамкой по строкам: AVG(revenue) OVER (ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW). Это среднее по текущей строке и шести предыдущим — итого 7 точек. Важно, что данные должны быть по одной строке на день без пропусков, иначе «6 PRECEDING» захватит не 6 календарных дней, а 6 строк, которые могут покрывать больший интервал. Если пропуски есть, сначала достраивают календарь через generate_series и LEFT JOIN.

Чем ROWS отличается от RANGE в оконной рамке?

ROWS BETWEEN N PRECEDING считает физические строки: ровно N предыдущих строк, независимо от значений. RANGE BETWEEN считает по значению колонки из ORDER BY: например, RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW возьмёт все строки, чья дата попадает в окно 7 календарных дней — это устойчиво к пропущенным датам и к нескольким записям в один день. На собесе именно этот нюанс любят: ROWS удобен на плотных рядах, RANGE — когда даты рваные или есть дубли.

Как посчитать rolling sum за 30 дней, если в данных есть пропуски дат?

Два пути. Первый — использовать RANGE BETWEEN INTERVAL '29 days' PRECEDING AND CURRENT ROW, тогда окно определяется по фактическим датам и пропуски не мешают. Второй — достроить непрерывный календарь: generate_series(min_date, max_date, interval '1 day') AS d, LEFT JOIN с данными, COALESCE(orders, 0), и уже поверх плотного ряда применить ROWS BETWEEN 29 PRECEDING. Второй способ надёжнее, если нужно, чтобы дни без данных считались нулями, а не выпадали из окна.

Как сделать так, чтобы среднее считалось только при полном окне?

По умолчанию в начале ряда окно неполное: на второй день среднее берётся всего по 2 точкам. Чтобы отсекать неполные окна, добавляют условие через COUNT: CASE WHEN COUNT(*) OVER (ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) = 7 THEN AVG(...) OVER (...) END. Тогда для первых шести дней вернётся NULL, и на графике сглаженная линия стартует только там, где окно набралось полностью — это ровно то, чего ждут в аккуратном дашборде.

Зачем на практике вообще нужно скользящее среднее?

Сырая дневная метрика сильно шумит и содержит недельную сезонность: по выходным заказов меньше, в понедельник всплеск. 7-дневное скользящее убирает эффект дня недели, потому что окно всегда покрывает полную неделю, а 28-дневное сглаживает ещё и месячные колебания. На собеседовании часто спрашивают именно «почему 7 дней, а не 5» — правильный ответ про кратность недельному циклу. Rolling-метрики — стандарт для трендов retention, выручки и DAU в продуктовой аналитике.

Связанные темы SQL

Агрегации · JOIN-запросы · Оконные функции · CTE (Common Table Expressions) · Подзапросы · Работа с датами · Строковые функции · Условная логика

Открыть весь SQL-тренажёр (565 задач) →