漏斗转化率不能用group by直接算,因为每步用户基数动态变化且需保持用户路径关联;必须按用户粒度标记完成状态,用row_number()排序+case when标记+窗口函数逐层计算分母,避免重复计数与中间步骤丢失。

漏斗转化率为什么不能用 GROUP BY 直接算
因为漏斗每一步的用户基数在变化,比如 100 人访问首页,其中 60 人点击商品页,30 人下单——你不能对每个步骤单独 GROUP BY step 再除,那样会丢失用户级路径关联。必须按用户粒度先标记是否完成各步,再逐层累计计数。
用 ROW_NUMBER() + 条件标记识别用户完成状态
核心是把每个用户的事件流按时间排序,再判断他是否走到某一步。常见错误是直接对原始日志 COUNT(DISTINCT user_id) 然后硬除,结果会高估(同一用户多次触发某步被重复计)。正确做法是:先用 ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time) 排序,再用 CASE WHEN 标记该用户是否「至少达成」某步骤。
- 假设步骤顺序为
'view'→'click'→'order',需确保时间戳可比(避免用event_time字段,而不用log_time) - 标记逻辑示例:
CASE WHEN MAX(CASE WHEN event_type = 'view' THEN 1 ELSE 0 END) OVER (PARTITION BY user_id) = 1 THEN 1 ELSE 0 END,这样每个用户只贡献 1 次 - 别用
WHERE event_type IN ('view','click','order')提前过滤——会丢掉中间缺失步骤的用户,影响分母准确性
用 COUNT(DISTINCT) OVER 配合窗口帧计算逐层分母
漏斗转化率 = 当前步人数 / 上一步人数,所以关键是要让每行都能拿到「上一步的去重用户数」。不能靠子查询嵌套,要用窗口函数把分母「拉平」到当前行。
- 先生成每步的用户完成标记表(如
has_view,has_click,has_order),每列都是 0/1 - 然后用
COUNT(DISTINCT CASE WHEN has_view = 1 THEN user_id END) OVER ()得到总访问人数(首步分母) - 第二步分母不是
COUNT(DISTINCT user_id WHERE has_click = 1),而是上一步的值——所以要把首步计数作为常量列,用FIRST_VALUE()或直接MAX()over 整个结果集 - PostgreSQL 支持
COUNT(DISTINCT user_id) FILTER (WHERE has_view = 1) OVER (),但 MySQL 8.0 不支持FILTER,得用SUM(has_view)+COUNT(DISTINCT)组合模拟
MySQL 8.0 下避免 DISTINCT 在窗口中报错的实际写法
MySQL 8.0 的 COUNT(DISTINCT ... ) OVER () 会报错 ERROR 3577: Window function 'count' does not support DISTINCT,这是最常卡住的地方。
- 绕过方法:先用
GROUP BY user_id汇总各用户是否完成各步,再在外层用SUM()窗口聚合——例如SUM(has_view) OVER ()就是完成首步的用户数(前提是已去重) - 关键前置动作:必须先
SELECT DISTINCT user_id, step_flag FROM raw_events做用户级宽表,否则SUM(has_view)会重复计算 - 如果原始数据有重复事件(如前端误发、重试日志),务必先用
ROW_NUMBER() OVER (PARTITION BY user_id, event_type ORDER BY event_time)去重,取rn = 1










