问题描述
我想根据存储在 ClickHouse 中的埋点数据进行漏斗分析。让我们为漏斗分析定义一些元素:
-
一系列事件:A (event_id = 1) -> B (event_id = 2) -> C (event_id = 3)
-
时间段:0 (event_ms) ~ 500 (event_ms)
-
时间窗口:100 (event_ms)
我想知道,对于每个用户,是否有一个事件系列(A->B->C)发生在时间段内,并且A和C之间的间隔在时间窗口内。
这是我的测试数据集:
CREATE TABLE test_dataset
(
`event_id` UInt64,`event_ms` UInt64,`uid` UInt64 // user_id
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMMDD(toDate(event_ms))
ORDER BY (event_id,event_ms,uid)
SETTINGS index_granularity = 8192;
INSERT INTO TABLE test_dataset VALUES
(1,100,123),(1,120,130,150,345),180,(2,200,234),140,210,300,(3,250,290,270,345);
我使用 join
查找所有符合条件的事件系列:
SELECT
t1.event_ms,t2.event_ms,t3.event_ms,t4.event_ms,t1.uid,t2.uid,t3.uid,t4.uid
FROM
(SELECT
uid,event_ms
FROM funnel_join_test_1
WHERE
event_id = 1 AND event_ms >= 0 AND event_ms <= 500) as t1
ASOF left join
(SELECT
uid,event_ms
FROM funnel_join_test_1
WHERE
event_id = 2 AND event_ms >= 0 AND event_ms <= 500) as t2
ON t1.uid = t2.uid AND t1.event_ms < t2.event_ms
ASOF left join
(SELECT
uid,event_ms
FROM funnel_join_test_1
WHERE
event_id = 3 AND event_ms >= 0 and event_ms <= 500) as t3
ON t2.uid = t3.uid and t2.event_ms < t3.event_ms
ASOF left join
(SELECT
uid,event_ms
FROM funnel_join_test_1
WHERE
event_id = 3 AND event_ms >= 0 and event_ms <= 500) as t4
ON t3.uid = t4.uid and t4.event_ms < t1.event_ms + 100
WHERE t4.event_ms > 0;
以下是所有合格的活动系列:
┌─t1.event_ms─┬─t2.event_ms─┬─t3.event_ms─┬─t4.event_ms─┬─t1.uid─┬─t2.uid─┬─t3.uid─┬─t4.uid─┐
│ 180 │ 210 │ 270 │ 270 │ 345 │ 345 │ 345 │ 345 │
└─────────────┴─────────────┴─────────────┴─────────────┴────────┴────────┴────────┴────────┘
┌─t1.event_ms─┬─t2.event_ms─┬─t3.event_ms─┬─t4.event_ms─┬─t1.uid─┬─t2.uid─┬─t3.uid─┬─t4.uid─┐
│ 120 │ 150 │ 180 │ 180 │ 123 │ 123 │ 123 │ 123 │
└─────────────┴─────────────┴─────────────┴─────────────┴────────┴────────┴────────┴────────┘
┌─t1.event_ms─┬─t2.event_ms─┬─t3.event_ms─┬─t4.event_ms─┬─t1.uid─┬─t2.uid─┬─t3.uid─┬─t4.uid─┐
│ 130 │ 150 │ 180 │ 180 │ 123 │ 123 │ 123 │ 123 │
└─────────────┴─────────────┴─────────────┴─────────────┴────────┴────────┴────────┴────────┘
┌─t1.event_ms─┬─t2.event_ms─┬─t3.event_ms─┬─t4.event_ms─┬─t1.uid─┬─t2.uid─┬─t3.uid─┬─t4.uid─┐
│ 100 │ 150 │ 180 │ 180 │ 123 │ 123 │ 123 │ 123 │
└─────────────┴─────────────┴─────────────┴─────────────┴────────┴────────┴────────┴────────┘
然后我知道用户 123 和 345 在时间段内有这样的事件系列。在 ClickHouse 中使用 join 非常慢,有没有其他方法可以解决这个问题?
顺便说一句,我不需要知道所有符合条件的系列,我只想知道每个用户是否有一个这样的事件系列。
解决方法
有函数 windowFunnel 在滑动窗口中搜索事件链。
SELECT
uid,windowFunnel(100)(event_ms,event_id = 1,event_id = 2,event_id = 3) AS chain_len
FROM test_dataset
WHERE (event_ms > 0) AND (event_ms < 500)
GROUP BY uid;
结果:
┌─uid─┬─chain_len─┐
│ 234 │ 0 │
│ 345 │ 3 │
│ 123 │ 3 │
└─────┴───────────┘
它返回匹配的链长度,因此对于用户 345
和 123
,我们有 3
表示匹配整个链。
如果我们将 window
减少到 10
,它只会找到链的开头,并且由于条件 timestamp of event 2 <= timestamp of event 1 + window
不成立而不会匹配其他事件。
SELECT
uid,windowFunnel(10)(event_ms,event_id = 3) AS chain_len
FROM test_dataset
WHERE (event_ms > 0) AND (event_ms < 500)
GROUP BY uid
结果:
┌─uid─┬─chain_len─┐
│ 234 │ 0 │
│ 345 │ 1 │
│ 123 │ 1 │
└─────┴───────────┘
因此,要检查用户是否存在这样的链,您可以检查 windowFunnel
是否匹配了适当数量的事件。
时间间隔限制(时间周期:0 (event_ms) ~ 500 (event_ms)),在WHERE
子句中简单处理。
在周期外添加更多事件:
INSERT INTO TABLE test_dataset VALUES (1,600,234),(2,601,(3,602,234);
然后检查:
SELECT
uid,event_id = 3) AS chain_len
FROM test_dataset
WHERE (event_ms > 0) AND (event_ms < 500)
GROUP BY uid
结果:
┌─uid─┬─chain_len─┐
│ 234 │ 0 │
│ 345 │ 3 │
│ 123 │ 3 │
└─────┴───────────┘
没有WHERE
SELECT
uid,event_id = 3) AS chain_len
FROM test_dataset
GROUP BY uid
结果:
┌─uid─┬─chain_len─┐
│ 234 │ 3 │
│ 345 │ 3 │
│ 123 │ 3 │
└─────┴───────────┘