КПКейсПрактика
Все материалы
Продуктовая аналитикагайдстарт

SQL даты и время в аналитике: периоды, недели и возраст пользователя

Как работать с датами в SQL: строить полные периоды, группировать события по дням и неделям, считать возраст пользователя и не ошибаться на границах интервалов.

КПКейсПрактика21 июля 2026 г.11 мин
Два аналитика размещают события на календарной шкале и отмечают границы периодов.

Запрос “покажи метрику за июнь” звучит просто, пока не выясняется, что сегодня 30 июня, данные приходят в UTC, а текущий день ещё не закончился. Ошибка в датах редко вызывает падение SQL: запрос успешно выполнится и покажет убедительную, но несопоставимую цифру. Разберём безопасные границы периода и базовые операции с датами.

Коротко

Для периода используй левую включённую и правую исключённую границу: `date >= start` и `date < next_start`. Так запрос не зависит от количества часов, не дублирует события между соседними периодами и не смешивает полный день с текущим.

  • `date_trunc` округляет дату до начала дня, недели или месяца.
  • Для июня границы — `2026-06-01` включительно и `2026-07-01` исключительно.
  • Таймзона должна быть частью определения метрики.
  • Неполный текущий день сравнивай отдельно или исключай из истории.
  • Возраст пользователя — разница между событием и датой старта.

Полный месяц через две границы

Для июня не нужно вычислять “последнюю секунду месяца”. Следующий месяц уже является естественной правой границей. Это работает одинаково для DATE и TIMESTAMP и не создаёт проблем с миллисекундами.

Границы периода
ПериодЛевая границаПравая граница
Июнь2026-06-01 включительно2026-07-01 исключительно
Вторая неделя июня2026-06-08 включительно2026-06-15 исключительно
Последние 30 днейcurrent_date - interval '30 days'current_date исключительно
События за полный июнь
select\n  count(*) as events_count,\n  count(distinct user_id) as active_users\nfrom events\nwhere event_date >= date '2026-06-01'\n  and event_date < date '2026-07-01';

date_trunc: собрать данные по календарному периоду

`date_trunc` приводит timestamp к началу выбранного периода. Сначала выбери таймзону продукта, потом округляй дату и группируй результат. Если данные уже имеют тип DATE, достаточно группировать по самой колонке.

DAU по календарным неделям
select\n  date_trunc('week', event_date)::date as week_start,\n  count(distinct user_id) as active_users\nfrom events\nwhere event_name = 'core_action'\n  and event_date >= date '2026-06-01'\n  and event_date < date '2026-07-01'\ngroup by week_start\norder by week_start;
Неделя — это договорённость

Проверь, с какого дня начинается неделя в вашей BI-системе и совпадает ли это с SQL-движком. Календарная неделя и последние семь дней — разные окна.

Календарный период и rolling window

Календарный месяц отвечает на вопрос “что произошло в июне”. Rolling window отвечает на вопрос “что происходило за последние 30 дней на каждую дату”. Нельзя подменять один подход другим: rolling-график сглаживает границы месяцев, но может скрыть сезонный перелом.

Какое окно отвечает на вопрос
ОкноПример вопросаРиск
Календарный месяцКак прошёл июнь?неполный текущий месяц
Календарная неделяКак меняется недельный ритм?разная граница недели
Последние 30 днейКакой свежий тренд?смешение сезонов и кампаний

Возраст пользователя для когорт

Для retention, activation и LTV нужно знать, сколько дней прошло от старта пользователя до события. В учебной схеме стартом будет `signup_date`, а событием — `core_action`. Разница дат создаёт возраст когорты, который потом можно использовать в D1, D7 или D30.

Возраст события относительно регистрации
select\n  u.user_id,\n  u.signup_date,\n  e.event_date,\n  e.event_date - u.signup_date as days_since_signup\nfrom users as u\njoin events as e using (user_id)\nwhere e.event_name = 'core_action'\n  and e.event_date >= u.signup_date\norder by u.user_id, e.event_date;
  • D0 — событие в день старта.
  • D1 — событие на следующий календарный день.
  • D7 — событие через семь календарных дней.
  • Rolling 24 hours может отличаться от календарного D1.

Если в данных есть timestamp и таймзона

Событие в 23:30 UTC может быть уже следующим днём для пользователя в Москве. Поэтому сначала переведи timestamp в бизнесовую таймзону, а только потом применяй `date_trunc` или приводи значение к DATE. Таймзона — часть определения метрики, а не косметика формата.

День события в бизнесовой таймзоне
select\n  (occurred_at at time zone 'Europe/Moscow')::date as business_day,\n  count(distinct user_id) as active_users\nfrom events_with_timestamp\nwhere occurred_at >= timestamp '2026-06-01 00:00:00+03'\n  and occurred_at < timestamp '2026-07-01 00:00:00+03'\ngroup by business_day\norder by business_day;
Не смешивай часовые пояса

Храни исходное время и отдельно фиксируй, в какой зоне строятся отчёты. Если один дашборд считает день по UTC, а другой по Москве, их DAU не обязан совпадать.

Частые ошибки с датами

Ошибки в датах обычно выглядят как маленькие расхождения, поэтому их легко списать на округление. На практике они могут менять когорты, зрелость retention и сравнение релизов.

  • Использовать `<= last_day 23:59:59` и потерять события с миллисекундами.
  • Включить сегодняшний неполный день в сравнение с полными днями.
  • Считать неделю как последние семь дней, хотя отчёт построен по календарным неделям.
  • Применить таймзону после группировки, а не до неё.
  • Сравнить когорты, которым доступны разные возраста D7 или D30.
  • Фильтровать timestamp строковым значением без явного типа и границы.

Мини-практика и следующий шаг

Посчитай DAU за полный июнь, затем сгруппируй его по неделям. Добавь days_since_signup и проверь D1 для пользователей из каждой когорты. В конце выпиши, какая таймзона и какое окно должны быть указаны рядом с метрикой в дашборде.

Продолжить чтение
Вся библиотека