Вопрос обработки временных рядов в реляционных базах данных возник на стыке аналитики и классического программирования с SQL. В SQL-92 нет специальных оконных функций, поэтмоу приходится изощряться с подзапросами для расчёта динамических метрик (скользящей суммы, среднего и так далее) и условий по времени.
Проблема — отсутствие стандартных инструментов для агрегации по скользящему окну, поиска предыдущего/следующего значения по времени, эффективной группировки по произвольному календарному интервалу (например, расчёт недельных/месячных показателей).
Решение:
Используя только стандартные средства, применяют подзапросы на корреляцию для каждой строки или группировку по вычисляемому критерию (например, месяцу, неделе):
Пример кода:
-- Пример группировки по неделям без оконных функций SELECT YEAR(event_date) AS year, WEEK(event_date) AS week, SUM(value) AS total FROM timeseries GROUP BY YEAR(event_date), WEEK(event_date) ORDER BY year, week; -- Коррелированный подзапрос для поиска предыдущей записи SELECT t1.id, t1.event_date, t1.value, ( SELECT t2.value FROM timeseries t2 WHERE t2.event_date < t1.event_date ORDER BY t2.event_date DESC LIMIT 1 ) as prev_value FROM timeseries t1;
Ключевые особенности:
Всегда ли WEEK(event_date) однозначно определяет календарную неделю для любых дат?
Нет — разные СУБД (и даже параметры одной СУБД) определяют первую неделю года по-разному (например, ISO 8601 vs американская система), это может привести к разным результатам при агрегации. Нужно явно указывать режим работы функции или использовать YEARWEEK.
SELECT YEARWEEK(event_date, 1) -- 1: ISO неделя начинается с понедельника FROM timeseries;
Удаляет ли коррелированный подзапрос дубликаты автоматически при поиске предыдущего значения?
Нет, коррелированный подзапрос не фильтрует дубликаты по умолчанию. Если в таблице несколько событий на одну дату — подзапрос вернет первое из них по сортировке, но остальные проигнорирует.
Можно ли через GROUP BY агрегировать по датам, не учитывая время?
Да, но нужно явно отбросить часть времени, например, с помощью DATE(event_date) или TRUNC(event_date) в разных СУБД:
SELECT DATE(event_datetime), COUNT(*) FROM events GROUP BY DATE(event_datetime)
Команда считала недельную аналитику по функциям WEEK(date), не настроив параметры на ISO-8601. В итоге отчёты за первую неделю января "терялись" — часть событий относилась к декабрю прошлого года согласно американской логике. Аналитика не сходилась.
Плюсы:
Минусы:
Специалист внедрил YEARWEEK(date, 1) и таблицу календаря, существенно повысив согласованность отчётов между странами и бизнес-подразделениями.
Плюсы:
Минусы: