核心要点
漏斗每一步统计的是「去重用户数」,不是事件次数,必须 COUNT(DISTINCT user_id)
单表多事件用条件聚合:COUNT(DISTINCT CASE WHEN event='步骤' THEN user_id END)
转化率分两种:整体转化率(各步 / 首步)和逐级转化率(本步 / 上一步),要说清口径
易错点:严格漏斗要求按顺序逐级到达,需校验时间先后;忽略顺序会高估后段转化
标准回答
漏斗分析的核心是统计每个步骤的「去重用户数」,再算相邻步骤的转化率。单张事件表的常见写法是条件聚合:用 COUNT(DISTINCT CASE WHEN event=某步骤 THEN user_id END) 一次扫描算出各步用户数,再相除得到转化率。要区分整体转化率(每步用户 / 第一步用户)和逐级转化率(本步 / 上一步)。若业务要求「严格顺序漏斗」(必须 A 之后才算 B),还需用多次 LEFT JOIN 或子查询校验事件的时间先后,否则会把跳步用户也计入而高估转化。
-- 浏览 → 加购 → 下单 → 支付 四步漏斗的去重用户数与逐级转化率
WITH step_users AS (
SELECT
COUNT(DISTINCT CASE WHEN event = 'view' THEN user_id END) AS s1_view,
COUNT(DISTINCT CASE WHEN event = 'cart' THEN user_id END) AS s2_cart,
COUNT(DISTINCT CASE WHEN event = 'order' THEN user_id END) AS s3_order,
COUNT(DISTINCT CASE WHEN event = 'pay' THEN user_id END) AS s4_pay
FROM user_events
WHERE dt = '2026-06-25'
)
SELECT
s1_view, s2_cart, s3_order, s4_pay,
ROUND(s2_cart * 1.0 / s1_view, 4) AS cvr_view_cart, -- 浏览→加购
ROUND(s3_order * 1.0 / s2_cart, 4) AS cvr_cart_order, -- 加购→下单
ROUND(s4_pay * 1.0 / s3_order, 4) AS cvr_order_pay, -- 下单→支付
ROUND(s4_pay * 1.0 / s1_view, 4) AS cvr_overall -- 整体转化
FROM step_users;常见误区
⚠️ 常见踩坑
用 COUNT(*) 统计事件次数而非 COUNT(DISTINCT user_id),会因一个用户多次触发而虚高;以及忽略步骤顺序——只要触发过后段事件就计入,会把未经过前置步骤的用户算进来,严格漏斗需校验时间先后。
追问
追问 1:如何实现「严格按顺序」的漏斗(B 必须发生在 A 之后)?
用自连接或多次 LEFT JOIN 校验时间先后:先取每个用户第一次 view 的时间,再要求 cart 的时间晚于它、order 晚于 cart,依次约束。也可用窗口函数把每个用户的事件按时间排序后做序列匹配(funnel/sequence 函数,如 ClickHouse 的 windowFunnel),按设定的时间窗口判定用户走到了第几步,更适合大规模严格漏斗。
追问 2:漏斗加上时间窗口限制(如 30 分钟内完成)该怎么写?
在顺序校验的基础上加时间差约束:要求后一步事件时间与前一步(或首步)时间差在窗口内,例如 cart_time BETWEEN view_time AND view_time + INTERVAL 30 MINUTE。用 windowFunnel 类函数可直接传入 30 分钟窗口参数;纯标准 SQL 则在 JOIN 的 ON/WHERE 里加上时间差条件即可。
延伸学习
与本题相关的知识库文章、术语、工具与行业资讯。