核心要点

  • 漏斗每一步统计的是「去重用户数」,不是事件次数,必须 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 或子查询校验事件的时间先后,否则会把跳步用户也计入而高估转化。

sql
-- 浏览 → 加购 → 下单 → 支付 四步漏斗的去重用户数与逐级转化率
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 里加上时间差条件即可。

延伸学习

与本题相关的知识库文章、术语、工具与行业资讯。