Как сгруппировать данные по времени в ClickHouse и заполнить недостающие данные нулями / 0 - PullRequest
0 голосов
/ 08 мая 2018

Предположим, у меня есть заданный диапазон времени. Для объяснения, давайте рассмотрим что-то простое, например, весь 2018 год. Я хочу запрашивать данные из ClickHouse в виде суммирования за каждый квартал, поэтому результат должен быть 4 строки.

Проблема в том, что у меня есть данные только за два квартала, поэтому при использовании GROUP BY quarter возвращаются только две строки.

SELECT
     toStartOfQuarter(created_at) AS time,
     sum(metric) metric
 FROM mytable
 WHERE
     created_at >= toDate(1514761200) AND created_at >= toDateTime(1514761200)
    AND
     created_at <= toDate(1546210800) AND created_at <= toDateTime(1546210800)
 GROUP BY time
 ORDER BY time

1514761200 - 2018-01-01
1546210800 - 2018-12-31

Возвращает:

time       metric
2018-01-01 345
2018-04-01 123

А мне нужно:

time       metric
2018-01-01 345
2018-04-01 123
2018-07-01 0
2018-10-01 0

Это упрощенный пример, но в случае реального использования агрегирование будет, например,. 5 минут вместо четверти, и GROUP BY будет иметь хотя бы еще один атрибут, такой как GROUP BY attribute1, time, поэтому желаемый результат -

time        metric  attribute1
2018-01-01  345     1
2018-01-01  345     2
2018-04-01  123     1
2018-04-01  123     2
2018-07-01  0       1
2018-07-01  0       2
2018-10-01  0       1
2018-10-01  0       2

Есть ли способ как-нибудь заполнить весь данный интервал? Как InfluxDB имеет fill аргумент для группы или TimescaleDb time_bucket() функция с generate_series() Я пытался искать документацию ClickHouse и проблемы с GitHub, и кажется, что это еще не реализовано, поэтому вопрос, возможно, заключается в том, есть ли обходной путь.

Ответы [ 3 ]

0 голосов
/ 03 сентября 2018

В качестве альтернативы для функции numbers() в некоторых случаях могут быть полезны range и функции массива.

Пример: для каждой пары (id1, id2) должны быть сгенерированы даты из предыдущих 7 дней.

SELECT
  id1,
  id2,
  arrayJoin(
    arrayMap( x -> today() - 7 + x, range(7) )
  ) as date2
FROM table
WHERE date >= now() - 7
GROUP BY id1, id2

Результат этого выбора можно использовать в UNION ALL для заполнения «дыр» в данных.

SELECT id1, id2, date, sum(column1)
FROM (
  SELECT
    id1,
    id2,
    date,
    column1 
  FROM table
  WHERE date >= now() - 7

  UNION ALL 

  SELECT
    id1,
    id2,
    arrayJoin(
      arrayMap( x -> today() - 7 + x, range(7) )
    ) as date2,
    0 as column1
  FROM table
  WHERE date >= now() - 7
  GROUP BY id1, id2
)
GROUP BY id1, id2, date
ORDER BY date, id1, id2
0 голосов
/ 06 июля 2019

Вот как я это сделал для часовых периодов (необходимо визуализировать это в Графане) спасибо @filimonov и @ mikhail

SELECT t, SUM(metric) as metric FROM (
    SELECT 
        arrayJoin(
          arrayMap( x -> toStartOfHour(addHours(toDateTime($from),x)),
              range(toUInt64(
                  dateDiff('hour', 
                      toDateTime($from), 
                      toDateTime($to)) + 1)))
        ) as t,
        0 as metric

    UNION ALL

    SELECT
        toStartOfHour(my_date) as t,
        COUNT(metric)
        FROM my_table
        WHERE t BETWEEN toDateTime($from) AND toDateTime($to)
        GROUP BY t
)
GROUP BY t ORDER BY t

Так, например, для диапазона от 2019-01-01 до 2019-01-02 это даст вам:

SELECT t, SUM(metric) as metric FROM (
    SELECT 
        arrayJoin(
          arrayMap( x -> toStartOfHour(addHours(toDateTime('2019-01-01 00:00:00'),x)),
              range(toUInt64(
                  dateDiff('hour', 
                      toDateTime('2019-01-01 00:00:00'), 
                      toDateTime('2019-01-02 00:00:00')) + 1)))
        ) as t,
        0 as metric

    UNION ALL

    SELECT
        toStartOfHour(my_date) as t,
        COUNT(1) as metric
        FROM my_table
        WHERE t BETWEEN toDateTime('2019-01-01 00:00:00') AND toDateTime('2019-01-02 00:00:00')
        GROUP BY t
)
GROUP BY t ORDER BY t;
t                  |metric|
-------------------|------|
2019-01-01 00:00:00|     0|
2019-01-01 01:00:00|     0|
2019-01-01 02:00:00|     0|
2019-01-01 03:00:00|     0|
2019-01-01 04:00:00|     0|
2019-01-01 05:00:00|     0|
2019-01-01 06:00:00|     0|
2019-01-01 07:00:00|105702|
2019-01-01 08:00:00|113315|
2019-01-01 09:00:00|149837|
2019-01-01 10:00:00|185314|
2019-01-01 11:00:00|246106|
2019-01-01 12:00:00|323036|
2019-01-01 13:00:00|     0|
2019-01-01 14:00:00|409160|
2019-01-01 15:00:00|379113|
2019-01-01 16:00:00|256634|
2019-01-01 17:00:00|286601|
2019-01-01 18:00:00|280039|
2019-01-01 19:00:00|248504|
2019-01-01 20:00:00|218642|
2019-01-01 21:00:00|186152|
2019-01-01 22:00:00|148478|
2019-01-01 23:00:00|109721|
2019-01-02 00:00:00|     0|
0 голосов
/ 14 мая 2018

Вы можете сгенерировать нулевые значения, используя функцию «число». Затем соедините ваш запрос и обнулите значения, используя UNION ALL, и уже в соответствии с полученными данными мы создаем GROUP BY.

Итак, ваш запрос будет выглядеть так:

SELECT SUM(metric),
       time
  FROM (
        SELECT toStartOfQuarter(toDate(1514761200+number*30*24*3600))  time,
               toUInt16(0) AS metric
          FROM numbers(30)

     UNION ALL 

          SELECT toStartOfQuarter(created_at) AS time,
               metric
          FROM mytable
         WHERE created_at >= toDate(1514761200)
           AND created_at >= toDateTime(1514761200)
           AND created_at <= toDate(1546210800)
           AND created_at <= toDateTime(1546210800)
       )
 GROUP BY time
 ORDER BY time

note toUInt16 (0) - нулевые значения должны быть того же типа, что и metrics

...