Clickhouse中高階函數使用案例


案例一:滑動窗口計算

需求描述

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-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 │
└────────────┴───────┘

4、預期效果

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

┌─────────dt─┬─vales─┬─max─┬─min─┬─ave─┬─sum─┐
│ 2020-01-01 │     5 │  15 │   5 │  10 │  30 │
│ 2020-01-02 │    10 │  20 │  10 │  15 │  45 │
│ 2020-01-03 │    15 │  25 │  15 │  20 │  60 │
│ 2020-01-04 │    20 │  30 │  20 │  25 │  75 │
│ 2020-01-05 │    25 │  35 │  25 │  30 │  90 │
│ 2020-01-06 │    30 │  40 │  30 │  35 │ 105 │
│ 2020-01-07 │    35 │  45 │  35 │  40 │ 120 │
│ 2020-01-08 │    40 │  50 │  40 │  45 │ 135 │
│ 2020-01-09 │    45 │  50 │  45 │47.5 │  95 │
│ 2020-01-10 │    50 │  50 │  50 │  50 │  50 │
└────────────┴───────┴─────┴─────┴─────┴─────┘

解決方案

方案一:

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─┐
│   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 │
└─────┴─────┴─────┘

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─┐
│   1 │ 1000 │ 101.1 │
│   1 │ 1001 │ 201.1 │
│   1 │ 1002 │ 301.1 │
│   1 │ 1005 │ 601.1 │
│   1 │ 1006 │ 701.1 │
│   1 │ 1008 │ 801.1 │
└─────┴──────┴───────┘

4、預期效果

┌─uid─┬───ts─┬──load─┐──diff─┐
│   1 │ 1000 │ 101.1 │ 100.0 │
│   1 │ 1001 │ 201.1 │ 100.0 │
│   1 │ 1002 │ 301.1 │ 100.0 │
│   1 │ 1005 │ 601.1 │ 100.0 │
│   1 │ 1006 │ 701.1 │ 100.0 │
│   1 │ 1008 │ 801.1 │  50.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   │ view    │  20200102 │
│ A   │ buy     │  20200103 │
│ B   │ login   │  20200101 │
│ B   │ view    │  20200102 │
│ 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   │ view    │  20200101 │
│ A   │ view    │  20200102 │
│ A   │ view    │  20200103 │
│ A   │ view    │  20200104 │
│ B   │ login   │  20200101 │
│ B   │ view    │  20200101 │
│ B   │ view    │  20200102 │
│ B   │ view    │  20200104 │
│ C   │ buy     │  20200102 │
│ C   │ login   │  20200101 │
│ C   │ view    │  20200102 │
│ C   │ view    │  20200103 │
└─────┴─────────┴───────────┘

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函數使用場景比較多,不僅僅是用來計算留存率


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM