Базовые операции с временными рядами
ClickHouse предоставляет несколько методов для работы с данными временных рядов, позволяя агрегировать, группировать и анализировать отдельные точки данных за разные периоды времени.
В этом разделе рассматриваются базовые операции, которые обычно используются при работе с данными, зависящими от времени.
К распространённым операциям относятся группировка данных по временным интервалам, обработка разрывов во временных рядах и вычисление изменений между периодами.
Эти операции можно выполнять с использованием стандартного синтаксиса SQL в сочетании со встроенными временными функциями ClickHouse.
Мы рассмотрим возможности ClickHouse для выполнения запросов к временным рядам на примере набора данных Wikistat (данные по просмотрам страниц Wikipedia):
CREATE TABLE wikistat
(
`time` DateTime,
`project` String,
`subproject` String,
`path` String,
`hits` UInt64
)
ENGINE = MergeTree
ORDER BY (time);
Заполним эту таблицу одним миллиардом записей:
INSERT INTO wikistat
SELECT *
FROM s3('https://ClickHouse-public-datasets.s3.amazonaws.com/wikistat/partitioned/wikistat*.native.zst')
LIMIT 1e9;
Агрегирование по временным интервалам
Наиболее распространённое требование — агрегировать данные по временным периодам, например получить общее количество хитов за каждый день:
SELECT
toDate(time) AS date,
sum(hits) AS hits
FROM wikistat
GROUP BY ALL
ORDER BY date ASC
LIMIT 5;
┌───────date─┬─────hits─┐
│ 2015-05-01 │ 25524369 │
│ 2015-05-02 │ 25608105 │
│ 2015-05-03 │ 28567101 │
│ 2015-05-04 │ 29229944 │
│ 2015-05-05 │ 29383573 │
└────────────┴──────────┘
Здесь мы использовали функцию toDate(), которая преобразует указанное время в тип Date. В качестве альтернативы можно формировать часовые батчи и затем фильтровать по конкретной дате:
SELECT
toStartOfHour(time) AS hour,
sum(hits) AS hits
FROM wikistat
WHERE date(time) = '2015-07-01'
GROUP BY ALL
ORDER BY hour ASC
LIMIT 5;
┌────────────────час──┬───хиты─┐
│ 2015-07-01 00:00:00 │ 656676 │
│ 2015-07-01 01:00:00 │ 768837 │
│ 2015-07-01 02:00:00 │ 862311 │
│ 2015-07-01 03:00:00 │ 829261 │
│ 2015-07-01 04:00:00 │ 749365 │
└─────────────────────┴────────┘
Функция toStartOfHour(), используемая здесь, приводит указанное время к началу часа.
Вы также можете группировать данные по году, кварталу, месяцу или дню.
Пользовательские интервалы группировки
Мы можем даже группировать по произвольным интервалам, например, по 5‑минутным интервалам, используя функцию toStartOfInterval().
Предположим, что мы хотим группировать по 4-часовым интервалам.
Мы можем задать интервал группировки с помощью предложения INTERVAL:
SELECT
toStartOfInterval(time, INTERVAL 4 HOUR) AS interval,
sum(hits) AS hits
FROM wikistat
WHERE date(time) = '2015-07-01'
GROUP BY ALL
ORDER BY interval ASC
LIMIT 6;
Или можно использовать функцию toIntervalHour()
SELECT
toStartOfInterval(time, toIntervalHour(4)) AS interval,
sum(hits) AS hits
FROM wikistat
WHERE date(time) = '2015-07-01'
GROUP BY ALL
ORDER BY interval ASC
LIMIT 6;
В любом случае, мы получаем следующие результаты:
┌────────────интервал─┬────хиты─┐
│ 2015-07-01 00:00:00 │ 3117085 │
│ 2015-07-01 04:00:00 │ 2928396 │
│ 2015-07-01 08:00:00 │ 2679775 │
│ 2015-07-01 12:00:00 │ 2461324 │
│ 2015-07-01 16:00:00 │ 2823199 │
│ 2015-07-01 20:00:00 │ 2984758 │
└─────────────────────┴─────────┘
Заполнение пустых групп
Во многих случаях мы работаем с разреженными данными, в которых некоторые интервалы отсутствуют. Это приводит к появлению пустых бакетов. Рассмотрим следующий пример, где мы группируем данные по часовым интервалам. В результате будет получена статистика, в которой для некоторых часов нет значений:
SELECT
toStartOfHour(time) AS hour,
sum(hits)
FROM wikistat
WHERE (project = 'ast') AND (subproject = 'm') AND (date(time) = '2015-07-01')
GROUP BY ALL
ORDER BY hour ASC;
┌────────────────hour─┬─sum(hits)─┐
│ 2015-07-01 00:00:00 │ 3 │ <- пропущенные значения
│ 2015-07-01 02:00:00 │ 1 │ <- пропущенные значения
│ 2015-07-01 04:00:00 │ 1 │
│ 2015-07-01 05:00:00 │ 2 │
│ 2015-07-01 06:00:00 │ 1 │
│ 2015-07-01 07:00:00 │ 1 │
│ 2015-07-01 08:00:00 │ 3 │
│ 2015-07-01 09:00:00 │ 2 │ <- пропущенные значения
│ 2015-07-01 12:00:00 │ 2 │
│ 2015-07-01 13:00:00 │ 4 │
│ 2015-07-01 14:00:00 │ 2 │
│ 2015-07-01 15:00:00 │ 2 │
│ 2015-07-01 16:00:00 │ 2 │
│ 2015-07-01 17:00:00 │ 1 │
│ 2015-07-01 18:00:00 │ 5 │
│ 2015-07-01 19:00:00 │ 5 │
│ 2015-07-01 20:00:00 │ 4 │
│ 2015-07-01 21:00:00 │ 4 │
│ 2015-07-01 22:00:00 │ 2 │
│ 2015-07-01 23:00:00 │ 2 │
└─────────────────────┴───────────┘
ClickHouse предоставляет модификатор WITH FILL для этого. Он заполнит все пропущенные часы нулевыми значениями, чтобы мы могли лучше понять распределение во времени:
SELECT
toStartOfHour(time) AS hour,
sum(hits)
FROM wikistat
WHERE (project = 'ast') AND (subproject = 'm') AND (date(time) = '2015-07-01')
GROUP BY ALL
ORDER BY hour ASC WITH FILL STEP toIntervalHour(1);
┌────────────────hour─┬─sum(hits)─┐
│ 2015-07-01 00:00:00 │ 3 │
│ 2015-07-01 01:00:00 │ 0 │ <- новое значение
│ 2015-07-01 02:00:00 │ 1 │
│ 2015-07-01 03:00:00 │ 0 │ <- новое значение
│ 2015-07-01 04:00:00 │ 1 │
│ 2015-07-01 05:00:00 │ 2 │
│ 2015-07-01 06:00:00 │ 1 │
│ 2015-07-01 07:00:00 │ 1 │
│ 2015-07-01 08:00:00 │ 3 │
│ 2015-07-01 09:00:00 │ 2 │
│ 2015-07-01 10:00:00 │ 0 │ <- новое значение
│ 2015-07-01 11:00:00 │ 0 │ <- новое значение
│ 2015-07-01 12:00:00 │ 2 │
│ 2015-07-01 13:00:00 │ 4 │
│ 2015-07-01 14:00:00 │ 2 │
│ 2015-07-01 15:00:00 │ 2 │
│ 2015-07-01 16:00:00 │ 2 │
│ 2015-07-01 17:00:00 │ 1 │
│ 2015-07-01 18:00:00 │ 5 │
│ 2015-07-01 19:00:00 │ 5 │
│ 2015-07-01 20:00:00 │ 4 │
│ 2015-07-01 21:00:00 │ 4 │
│ 2015-07-01 22:00:00 │ 2 │
│ 2015-07-01 23:00:00 │ 2 │
└─────────────────────┴───────────┘
Скользящие временные окна
Иногда нам не нужно привязываться к началу стандартных интервалов (например, началу суток или часа), а важно работать с временными окнами.
Допустим, мы хотим понять общее количество хитов за окно не по календарным дням, а за 24-часовой период, смещённый от 18:00.
Мы можем использовать функцию date_diff(), чтобы вычислить разницу между опорным временем и временем каждой записи.
В этом случае столбец day будет представлять разницу в днях (например, 1 день назад, 2 дня назад и т. д.):
SELECT
dateDiff('day', toDateTime('2015-05-01 18:00:00'), time) AS day,
sum(hits),
FROM wikistat
GROUP BY ALL
ORDER BY day ASC
LIMIT 5;
┌─day─┬─sum(hits)─┐
│ 0 │ 25524369 │
│ 1 │ 25608105 │
│ 2 │ 28567101 │
│ 3 │ 29229944 │
│ 4 │ 29383573 │
└─────┴───────────┘