ClickHouse中如何实现漏斗分析

时间:2021-03-17 06:19:36

标签: clickhouse

我想根据存储在 ClickHouse 中的埋点数据进行漏斗分析。让我们为漏斗分析定义一些元素:

  1. 一系列事件:A (event_id = 1) -> B (event_id = 2) -> C (event_id = 3)

  2. 时间段:0 (event_ms) ~ 500 (event_ms)

  3. 时间窗口: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 个答案:

答案 0 :(得分:2)

有函数 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 <= 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 │
└─────┴───────────┘
相关问题