ClickHouse中如何实现漏斗分析

Posted

技术标签:

【中文标题】ClickHouse中如何实现漏斗分析【英文标题】:How to realize funnel analysis in ClickHouse 【发布时间】:2021-03-17 06:19:36 【问题描述】:

我想根据存储在 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, 123), 
      (1, 130, 123), 
      (1, 150, 345), 
      (1, 180, 345), 
      (2, 150, 123), 
      (2, 200, 234), 
      (2, 140, 345),
      (2, 210, 345),
      (2, 300, 345),
      (3, 180, 123),
      (3, 250, 123),
      (3, 290, 234),
      (3, 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 很慢,有没有其他方法可以解决这个问题?

顺便说一句,我不需要知道所有合格的系列,我只想知道每个用户是否有一个这样的事件系列。

【问题讨论】:

【参考方案1】:

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

它返回匹配的链长度,因此对于用户345123,我们有3,这意味着整个链都匹配。

如果我们将window 减少到10,它将仅找到链的开头并且由于条件timestamp of event 2 &lt;= timestamp of event 1 + window 不成立而与其他事件不匹配。

SELECT
    uid,
    windowFunnel(10)(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 │         1 │
│ 123 │         1 │
└─────┴───────────┘

因此,要检查用户是否存在这样的链,您可以检查 windowFunnel 是否匹配适当数量的事件。

时间间隔限制(时间段:0(event_ms)~500(event_ms)),在WHERE子句中简单处理。

添加更多非周期事件:

INSERT INTO TABLE test_dataset VALUES (1, 600, 234), (2, 601, 234), (3, 602, 234);

然后检查:

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

没有WHERE

SELECT
    uid,
    windowFunnel(100)(event_ms, event_id = 1, event_id = 2, event_id = 3) AS chain_len
FROM test_dataset
GROUP BY uid

结果:

┌─uid─┬─chain_len─┐
│ 234 │         3 │
│ 345 │         3 │
│ 123 │         3 │
└─────┴───────────┘

【讨论】:

嗨@vdimir,感谢您的建议!我也发现了这个函数,但似乎它有一个关于重复时间戳的错误,请参阅更详细的描述here,您对此有什么想法吗?

以上是关于ClickHouse中如何实现漏斗分析的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ClickHouse 窗口漏斗模式?

Clickhouse(流量分析(一).漏斗分析案例)

Clickhouse(流量分析(三).路径分析案例)

如何创建内部漏斗分析?

ClickHouse场景和未来的一些发展方向

ClickHouse场景和未来的一些发展方向