【问题标题】:How to group by time bucket in ClickHouse and fill missing data with nulls/0s如何在 ClickHouse 中按时间段分组并用空值/0 填充缺失的数据
【发布时间】:2018-10-18 16:45:05
【问题描述】:

假设我有一个给定的时间范围。为了解释,让我们考虑一些简单的事情,比如 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

15147612002018-01-01
15462108002018-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 问题,似乎这还没有实现,所以问题可能是是否有任何解决方法。

【问题讨论】:

  • 'WITH FILL' 可以从 Clickhouse 19.14 开始使用。不幸的是,这似乎只在简单的情况下才能正常工作,而无需通过其他属性进行额外的分组。

标签: sql clickhouse


【解决方案1】:

您可以使用“数字”函数生成零值。然后使用 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

注意 UInt16(0) - 零值必须与 metrics 的类型相同

【讨论】:

  • 不知道number功能,可以很舒服的用来生成系列,谢谢!但是对于未知数量的属性,有什么方法可以做到这一点?
  • 在您的情况下,您可以计算您的经期中的季度数,这就是您需要的数字
  • 是的,这可以通过从 timerange 派生来动态完成,但我的意思是 Q 中提到的attribute1。无论如何,同时我通过加入子查询来设法使其工作attribute1 值。现在唯一缺少的是在 postgres 中对 CTE 的支持。谢谢!
【解决方案2】:

从 ClickHouse 19.14 开始,您可以使用 WITH FILL 子句。它可以用这种方式填满宿舍:

WITH
    (
        SELECT toRelativeQuarterNum(toDate('1970-01-01'))
    ) AS init
SELECT
    -- build the date from the relative quarter number
    toDate('1970-01-01') + toIntervalQuarter(q - init) AS time,
    metric
FROM
(
    SELECT
        toRelativeQuarterNum(created_at) AS q,
        sum(rand()) AS metric
    FROM
    (
        -- generate some dates and metrics values with gaps
        SELECT toDate(arrayJoin(range(1514761200, 1546210800, ((60 * 60) * 24) * 180))) AS created_at
    )
    GROUP BY q
    ORDER BY q ASC WITH FILL FROM toRelativeQuarterNum(toDate(1514761200)) TO toRelativeQuarterNum(toDate(1546210800)) STEP 1
)

┌───────time─┬─────metric─┐
│ 2018-01-01 │ 2950782089 │
│ 2018-04-01 │ 2972073797 │
│ 2018-07-01 │          0 │
│ 2018-10-01 │  179581958 │
└────────────┴────────────┘

【讨论】:

  • 如果不清楚,range() 输入值是纪元时间。使用epochconverter.com 方便转换。
【解决方案3】:

在某些情况下,range 和数组函数可以替代 numbers() 函数。

示例:应为每对 (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

【讨论】:

    【解决方案4】:

    这是我在小时桶中的做法(需要在 Grafana 中可视化) 感谢@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|
    

    【讨论】:

      猜你喜欢
      • 2020-04-08
      • 1970-01-01
      • 2021-12-25
      • 2013-04-27
      • 2021-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多