Clickhouse中高阶函数使用案例

文章摘要:Clickhouse中高阶函数的一写常见使用案例,包含滑动窗口计算、分组计算TopK值,时序数据求diff、漏斗函数、留存函数

案例一:滑动窗口计算

需求描述

1、创建表

CREATE TABLE test_windows_function
(
    `dt` Date,
    `vales` Int64
)
ENGINE = Memory

2、插入数据

insert into test_windows_function(dt,vales) values('2020-01-01',5),('2020-01-02',10),('2020-01-03',15),('2020-01-04',20),('2020-01-05',25),('2020-01-06',30),('2020-01-07',35),('2020-01-08',40),('2020-01-09',45),('2020-01-10',50)

3、查看数据

┌─────────dt─┬─vales─┐
│ 2020-01-015 │
│ 2020-01-0210 │
│ 2020-01-0315 │
│ 2020-01-0420 │   
│ 2020-01-0525 │
│ 2020-01-0630 │
│ 2020-01-0735 │
│ 2020-01-0840 │
│ 2020-01-0945 │
│ 2020-01-1050 │
└────────────┴───────┘

4、预期效果

求top3(N)的最大值、最小值、平均值、求和

┌─────────dt─┬─vales─┬─max─┬─min─┬─ave─┬─sum─┐
│ 2020-01-0151551030 │
│ 2020-01-021020101545 │
│ 2020-01-031525152060 │
│ 2020-01-042030202575 │
│ 2020-01-052535253090 │
│ 2020-01-0630403035105 │
│ 2020-01-0735453540120 │
│ 2020-01-0840504045135 │
│ 2020-01-0945504547.595 │
│ 2020-01-105050505050 │
└────────────┴───────┴─────┴─────┴─────┴─────┘

解决方案

方案一:

SQL逻辑

  • 使用with语句按照dt字段排序,得到原始数据 [5,10,15,20,25,30,35,40,45,50]
  • arraySlice(vales_arr, shift + 1, 3),每次都去前三个数。eg:第一次 [5,10,15]
  • arrayReduce(‘avg’, arraySlice(vales_arr, shift + 1, 3),计算平均数
  • arrayMap函数,类似做个for循环
WITH
    (
        SELECT groupArray(vales)
        FROM
        (
            SELECT vales
            FROM test_windows_function
            ORDER BY dt ASC
        )
    ) AS vales_arr
SELECT
    vales_arr,
    arrayMap(shift -> arrayReduce('avg', arraySlice(vales_arr, shift + 1, 3)), range(length(vales_arr))) AS avg,
    arrayMap(shift -> arrayReduce('max', arraySlice(vales_arr, shift + 1, 3)), range(length(vales_arr))) AS max,
    arrayMap(shift -> arrayReduce('min', arraySlice(vales_arr, shift + 1, 3)), range(length(vales_arr))) AS min,
    arrayMap(shift -> arrayReduce('sum', arraySlice(vales_arr, shift + 1, 3)), range(length(vales_arr))) AS sum

Row 1:
──────
vales_arr: [5,10,15,20,25,30,35,40,45,50]
avg:       [10,15,20,25,30,35,40,45,47.5,50]
max:       [15,20,25,30,35,40,45,50,50,50]
min:       [5,10,15,20,25,30,35,40,45,50]
sum:       [30,45,60,75,90,105,120,135,95,50]

方案二:

SELECT
    groupArrayMovingSum(3)(vales) AS sum,
    groupArrayMovingAvg(3)(vales) AS avg
FROM test_windows_function

┌─sum───────────────────────────────┬─avg───────────────────────────┐
│ [5,15,30,45,60,75,90,105,120,135][1,5,10,15,20,25,30,35,40,45] │
└───────────────────────────────────┴───────────────────────────────┘

TIPS

1、数据量大的时候效果性能必然是不好的~~

2、方案二是19版本出来的新函数,目前支持sum和avg哦~,并不支持max、min

案例二:分组计算TopK

需求描述

1、创建表

CREATE TABLE test_topK_function ( c_a Int32,c_b Int32,c_c Int32) ENGINE = Memory

2、插入数据

insert into test_topK_function ( c_a,c_b,c_c) values (1,2,5),(1,2,4),(1,3,3),(1,3,2),(1,4,1),(2,3,9),(2,3,7),(2,3,5),(2,4,3),(2,5,6),(3,3,9),(3,3,7),(3,3,5),(3,4,3),(3,5,6);

3、查看展示

SELECT *
FROM test_topK_function
ORDER BY c_a ASC

┌─c_a─┬─c_b─┬─c_c─┐
│   125 │
│   124 │
│   133 │
│   132 │
│   141 │
│   239 │
│   237 │
│   235 │
│   243 │
│   256 │
│   339 │
│   337 │
│   335 │
│   343 │
│   356 │
└─────┴─────┴─────┘

4、预期效果

根据c_a列进行分组,找c_c列的top2

┌─c_a─┬─topK(2)(c_c)─┐
│   3[9,7]        │
│   2[9,7]        │
│   1[5,4]        │
└─────┴──────────────┘

解决方案

方案一:

直接使用提供的topK函数实现

SELECT
    c_a,
    topK(2)(c_c)
FROM test_topK_function
GROUP BY c_a

┌─c_a─┬─topK(2)(c_c)─┐
│   3[9,7]        │
│   2[9,7]        │
│   1[5,4]        │
└─────┴──────────────┘

方案二:

下面的这个SQL是上面的topK实现的逻辑

SELECT
    c_a,
    groupArray(2)(c_c)
FROM
(
    SELECT *
    FROM test_topK_function
    ORDER BY
        c_a ASC,
        c_c DESC
)
GROUP BY c_a

┌─c_a─┬─groupArray(2)(c_c)─┐
│   3[9,7]              │
│   2[9,7]              │
│   1[5,4]              │
└─────┴────────────────────┘

TIPS

  • 直接使用topK吧,原生提供。

案例三:时序数据求差值timeSeriesGroupRateSum

需求描述

1、创建表

CREATE TABLE default.test_ts
(
    `uid` UInt64,
    `ts` Int64,
    `load` Float64
)
ENGINE = Memory
# 注意这里的时间字段只能是Int64
# clickhouse版本必须>=19

2、插入数据

insert into test_ts(uid,ts,load) values (1,1000,101.1),(1,1001,201.1),(1,1002,301.1),(1,1005,601.1),(1,1006,701.1),(1,1008,801.1);

3、查看数据

SELECT *
FROM test_ts

┌─uid─┬───ts─┬──load─┐
│   11000101.1 │
│   11001201.1 │
│   11002301.1 │
│   11005601.1 │
│   11006701.1 │
│   11008801.1 │
└─────┴──────┴───────┘

4、预期效果

┌─uid─┬───ts─┬──load─┐──diff─┐
│   11000101.1100.0 │
│   11001201.1100.0 │
│   11002301.1100.0 │
│   11005601.1100.0 │
│   11006701.1100.0 │
│   11008801.150.0 │
└─────┴──────┴───────┘ ──────┘
根据ts字段,求load字段的差值,注意1002-1005有时间跳变,会自动处理

解决方案

SELECT timeSeriesGroupRateSum(uid, ts, load)
FROM test_ts

┌─timeSeriesGroupRateSum(uid, ts, load)───────────────────────────────────────────┐
│ [(1000,0),(1001,100),(1002,100.00000000000003),(1005,100),(1006,100),(1008,50)] │
└─────────────────────────────────────────────────────────────────────────────────┘

TIPS

  • clicktimeSeriesGroupRateSum很强大,求diff的场景非常常见,比如监控取得累加值,需要求差
  • clicktimeSeriesGroupRateSum不仅仅是针对单uid,还可以处理多uid,也就是多个维度的数据对其

案例四:漏斗函数windowFunnel

需求描述

场景描述,登录 --> 查看详情 --> 收藏 --> 购买,查看有多少用户在给定的时间创建依次执行上述操作。

1、创建表

CREATE TABLE test_window_funnel( uid String, eventid String, eventTime UInt64) ENGINE = Memory

2、插入数据

insert into test_window_funnel(uid,eventid,eventTime) values ('A','login',20200101),('A','view',20200102),('A','buy',20200103),('B','login',20200101),('B','view',20200102),('C','login',20200101),('C','buy',20200102); ('A',)

3、查看数据

SELECT *
FROM test_window_funnel

┌─uid─┬─eventid─┬─eventTime─┐
│ A   │ login   │  20200101 │
│ A   │ view20200102 │
│ A   │ buy     │  20200103 │
│ B   │ login   │  20200101 │
│ B   │ view20200102 │
│ C   │ login   │  20200101 │
│ C   │ buy     │  20200102 │
└─────┴─────────┴───────────┘
A用户:从login(登录) -> view(详情页) -> buy(购买) 花了3天
B用户:从login(登录) -> view(详情页) 花了2天
C用户:从login(登录) -> viewbuy(购买) 花了2

4、预期效果

计算2天内完成 从login(登录) -> view(详情页) -> buy(购买) 的用户
┌─uid─┬─res─┐
│ B   │   2 │
│ C   │   1 │
│ A   │   3 │
└─────┴─────┘
A=3说明A用户3件事情都做了

解决方案

SELECT
    uid,
    windowFunnel(2)(eventTime, eventid = 'login', eventid = 'view', eventid = 'buy') AS res
FROM test_window_funnel
GROUP BY uid

┌─uid─┬─res─┐
│ B   │   2 │
│ C   │   1 │
│ A   │   3 │
└─────┴─────┘
A login -> view -> buy 2天内3件事均满足,结果为3
B login -> view        2天内满足2件事,结果为2
C login -> buy         2天内按照顺序只满足1件事,结果为1

TIPS

  • 主要是计算转化和留存率的,使用上也比较方便

案例五:留存函数retention

需求描述

计算网站的留存率,比如某个新上的页面或者功能,用户会不会每天都来

1、创建表

复用上面的test_window_funnel表结构

2、插入数据

新增一些数据
insert into test_window_funnel(uid,eventid,eventTime) values ('A','view',20200101),('A','view',20200103),('A','view',20200104),('B','view',20200104),('B','view',20200101),('C','view',20200103),('C','view',20200102);

3、查看数据

SELECT *
FROM test_window_funnel
ORDER BY
    uid ASC,
    eventid ASC,
    eventTime ASC

┌─uid─┬─eventid─┬─eventTime─┐
│ A   │ buy     │  20200103 │
│ A   │ login   │  20200101 │
│ A   │ view20200101 │
│ A   │ view20200102 │
│ A   │ view20200103 │
│ A   │ view20200104 │
│ B   │ login   │  20200101 │
│ B   │ view20200101 │
│ B   │ view20200102 │
│ B   │ view20200104 │
│ C   │ buy     │  20200102 │
│ C   │ login   │  20200101 │
│ C   │ view20200102 │
│ C   │ view20200103 │
└─────┴─────────┴───────────┘

4、预期效果

# 20200101,20200102,20200103,20200104 用户是否访问view(详情页)
A [1,1,1,1] ~ 4天连续访问了
B [1,1,0,1] ~3天没有访问
C [0,0,0,0] ~ 因为第一天没有访问,所有后面的全都是0

解决方案

SELECT
    uid,
    retention(eventTime = 20200101, eventTime = 20200102, eventTime = 20200103, eventTime = 20200104) AS r
FROM test_window_funnel
WHERE eventid = 'view'
GROUP BY uid

┌─uid─┬─r─────────┐
│ B   │ [1,1,0,1] │
│ C   │ [0,0,0,0] │
│ A   │ [1,1,1,1] │
└─────┴───────────┘

TIPS

  • retention函数使用场景比较多,不仅仅是用来计算留存率
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章