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函數使用場景比較多,不僅僅是用來計算留存率
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章