漏斗分析不能只用GROUP BY,因需基于同一用户标识去重后用CASE WHEN+SUM计算各步人数及转化率,且须处理时序逻辑和去重陷阱。

漏斗分析为什么不能只用GROUP BY
因为漏斗每一步是独立判断的,用户可能只走到第二步就退出,但传统分组会把人固定在某个层级里。比如用 GROUP BY step 只能统计“当前步有多少人”,没法算出“从第一步到第二步的转化率”。必须让同一行数据参与多个条件的计数,CASE WHEN + SUM 是最直接的解法。
常见错误现象:SELECT COUNT(*) FROM ... GROUP BY step 得到的是各环节人数,但无法对齐起点人数做除法;或者误用 WHERE step IN (1,2,3) 导致中间步骤缺失的用户被整个过滤掉。
- 漏斗必须基于同一个用户标识(如
user_id)去重后统计,否则重复行为会虚高人数 -
CASE WHEN中的条件要互斥且覆盖全路径,比如用step = 1而不是step ,避免叠加计数 - 别在
SUM外层再套COUNT,SUM(CASE WHEN ... THEN 1 ELSE 0 END)才是正确模式
CASE WHEN + SUM 的标准写法长什么样
核心就是为每一步定义一个“是否完成该步”的指标列,然后汇总。不需要子查询,也不依赖窗口函数,兼容 MySQL 5.7、PostgreSQL、SQL Server 等主流引擎。
SELECT SUM(CASE WHEN step = 1 THEN 1 ELSE 0 END) AS cnt_step1, SUM(CASE WHEN step = 2 THEN 1 ELSE 0 END) AS cnt_step2, SUM(CASE WHEN step = 3 THEN 1 ELSE 0 END) AS cnt_step3, ROUND(SUM(CASE WHEN step = 2 THEN 1 ELSE 0 END) * 100.0 / NULLIF(SUM(CASE WHEN step = 1 THEN 1 ELSE 0 END), 0), 2) AS rate_1_to_2 FROM events WHERE user_id IS NOT NULL;
注意:NULLIF 是防止除零错误的关键,很多人在计算转化率时漏掉它,导致整条 SQL 报错或返回 NULL。
- 所有
CASE WHEN必须写ELSE 0,不写会导致该行对应列为NULL,SUM会跳过它,结果偏小 - 如果原始表里一个用户有多次 step=1 记录,要先按
user_id去重(用DISTINCT或子查询),否则第一步人数会被高估 - 字段名如
cnt_step1建议保持语义清晰,避免用step1这类易混淆的缩写
当漏斗依赖顺序或时间窗口时怎么处理
真实场景中,“完成第二步”隐含了“已走过第一步且时间在之后”。纯 CASE WHEN 无法表达时序逻辑,必须提前聚合或加条件限制。
典型错误:直接在原始行为日志上跑上面 SQL,结果发现 cnt_step2 > cnt_step1——因为有些用户先做了第二步再做第一步(比如测试账号乱点)。
- 稳妥做法是先用子查询或 CTE 提取每个用户的首达步骤时间,例如:
SELECT user_id, MIN(CASE WHEN step = 1 THEN event_time END) AS t1, MIN(CASE WHEN step = 2 THEN event_time END) AS t2 FROM events GROUP BY user_id - 然后在外层判断
t2 > t1 AND t1 IS NOT NULL AND t2 IS NOT NULL,再统计有效转化人数 - 如果数据量大,这种两层聚合会影响性能,可考虑预计算用户路径表(如
user_id, path_string, steps_completed),避免每次实时拼
和 COUNT(DISTINCT ...) 混用时的坑
漏斗常需“各步去重用户数”,但 COUNT(DISTINCT user_id) 不能直接塞进 CASE WHEN 里——语法不支持。得换结构。
错误写法:SUM(CASE WHEN step = 1 THEN COUNT(DISTINCT user_id) ELSE 0 END) → 报错。
- 正确方式是把
COUNT(DISTINCT ...)放在最外层,每个步骤单独子查询,再用JOIN或UNION ALL合并,例如:(SELECT COUNT(DISTINCT user_id) FROM events WHERE step = 1) AS cnt_step1 - 或者用条件聚合配合
MAX/MIN模拟,但仅适用于小规模数据,且逻辑更绕 - MySQL 8.0+ 可用窗口函数 +
ROW_NUMBER()去重后计数,但可读性下降,调试成本变高
真正难的不是写出来,而是确认你统计的“用户”是否符合业务定义——是注册 ID?设备 ID?还是埋点上报的匿名 ID?这个源头没对齐,后面所有数字都是幻觉。

















