不能用 group by 实现正确漏斗转化分析,因其仅统计各事件独立去重人数,无法保证后步用户属于前步子集,导致转化率倒挂;正确做法是先 group by user_id 构建统一用户池,再用 case when 判定各步骤完成情况。

不能用 GROUP BY 实现正确漏斗转化分析。它只能统计各事件的独立去重人数,无法保证后一步用户属于前一步用户的子集——这是漏斗的核心约束。直接 GROUP BY event_type 出来的“转化率”是伪指标,数值可能倒挂(比如 add_to_cart 人数 > view 人数),根本不能反映真实路径完成情况。
为什么 GROUP BY event_type 会算错转化率
它对每个 event_type 单独执行 COUNT(DISTINCT user_id),相当于把用户池切成了几块互不相干的碎片:
- 查
view:得到 1000 个user_id - 查
add_to_cart:得到 800 个user_id,但这 800 人里可能只有 600 人出现在上一步的 1000 人中 -
GROUP BY不提供“这个用户是否同时满足 step1 和 step2”的逻辑表达能力
GROUP BY 的唯一合理用途:单步 UV 快速探查
仅适合做初步数据质量检查或粗略分布观察,例如确认是否有明显异常值或拼写错误:
SELECT event_type, COUNT(DISTINCT user_id) AS uv FROM events WHERE event_time >= '2026-09-28' GROUP BY event_type ORDER BY uv DESC;
注意点:
- 务必加
WHERE时间过滤,否则全表扫描代价高 - 若发现
uv异常高(如view小于add_to_cart),大概率是数据未清洗(如event_type含空格或大小写混用) - 结果不能用于任何分母计算,更不能直接套用
ROUND(100.0 * next_uv / prev_uv, 2)
替代方案:必须用用户级聚合 + CASE WHEN
真正可行的写法是先锁定用户池,再对每个用户判别其是否达成各步骤:
SELECT
COUNT(*) FILTER (WHERE has_view = 1) AS uv_view,
COUNT(*) FILTER (WHERE has_cart = 1) AS uv_cart,
COUNT(*) FILTER (WHERE has_purchase = 1) AS uv_purchase,
ROUND(100.0 * COUNT(*) FILTER (WHERE has_cart = 1) / NULLIF(COUNT(*) FILTER (WHERE has_view = 1), 0), 2) AS rate_view_to_cart
FROM (
SELECT
user_id,
MAX(CASE WHEN event_type = 'view' THEN 1 ELSE 0 END) AS has_view,
MAX(CASE WHEN event_type = 'add_to_cart' THEN 1 ELSE 0 END) AS has_cart,
MAX(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) AS has_purchase
FROM events
WHERE user_id IS NOT NULL
AND event_type IN ('view', 'add_to_cart', 'purchase')
GROUP BY user_id
) t;
关键细节:
-
GROUP BY user_id是必须的,它构建了统一用户池 -
MAX(CASE ...)或COUNT(*) FILTER确保每个用户每步最多计 1 次,解决重复触发问题 - 仍不满足时间顺序要求——如果业务要求“加购必须在浏览之后”,此写法依然不够,得上
ROW_NUMBER()+LAG()
最容易被忽略的一点:即使你只关心“有没有做过”,也必须先 GROUP BY user_id 再聚合,而不是在原始事件表上直接 GROUP BY event_type。后者连最基本的用户去重一致性都保不住。










