漏斗题

类型:自编场景题(漏斗转化)| 题号:— | 链接:—

关联:SQL原理_04(漏斗章节)· SQL分析场景模板 · 好友申请 II :谁有最多的好友(同为列转行+聚合)

🎯 问题描述

表结构

1
event_log(user_id, event_name, event_time)

事件名:view、cart、order、pay。

需求

统计从"浏览 → 加购 → 下单 → 支付"的漏斗人数。

要求每一步必须发生在第一步之后,即按时间顺序:

1
view_time < cart_time < order_time < pay_time

输出:view_users、cart_users、order_users、pay_users。

说明:每个用户可能多次触发同一事件,需按最早时间归并成一行再比较顺序。

💻 解题思路

思路1:MIN(CASE WHEN) 归并 + 时间递增计数(用户解法)

核心逻辑

① 子查询 person_log:按 user_id 分组,用 MIN(CASE WHEN event_name = 'xx' THEN event_time END) 把每个事件的最早时间抽成一列(列转行 → 转回列);
② 外层按时间顺序逐级比较:走到下一步的前提是上一步时间更早,用 COUNT(CASE WHEN 条件 THEN 1 END) 数各步人数;
③ 缺 ELSE 时 CASE 返回 NULL,COUNT 只数非空,因此只统计"真的依次走到该步"的用户。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
WITH person_log AS (
SELECT
user_id,
MIN(CASE WHEN event_name = 'view' THEN event_time END) AS u_view,
MIN(CASE WHEN event_name = 'cart' THEN event_time END) AS u_cart,
MIN(CASE WHEN event_name = 'order' THEN event_time END) AS u_order,
MIN(CASE WHEN event_name = 'pay' THEN event_time END) AS u_pay
FROM event_log
GROUP BY user_id
)
SELECT
COUNT(CASE WHEN u_view IS NOT NULL THEN 1 END) AS view_users,
COUNT(CASE WHEN u_view < u_cart THEN 1 END) AS cart_users,
COUNT(CASE WHEN u_view < u_cart AND u_cart < u_order THEN 1 END) AS order_users,
COUNT(CASE WHEN u_view < u_cart AND u_cart < u_order AND u_order < u_pay THEN 1 END) AS pay_users
FROM person_log;

🔧 修正说明:

  1. 原文 u_cartTHEN 缺空格,已改为 u_cart THEN。
  2. 原文第一步写 COUNT(CASE WHEN u_view THEN 1 END) —— 直接拿时间做布尔判断,遇到 '0000-00-00' 这类假值会误判;应显式写 u_view IS NOT NULL 才准确。
  3. 时间比较也可以用 DATEDIFF(u_cart, u_view) > 0,语义等价。

📊 性能分析

  • 时间复杂度:O(n)O(n)(一次分组聚合即可,无 JOIN)。
  • 空间复杂度:O(1)O(1)(相对输入)。

思考

  • 这是严格有序漏斗:view < cart < order < pay 全部成立才算走完。若业务允许"乱序转化"(比如直接下单没加购),条件要相应放宽。
  • 若要算转化率,再除以 view_users 即可:cart_users / view_users 等。
  • 若同一用户同事件多天发生、且要求"同一会话内",需要额外按会话/时间窗切分。

📝 总结

漏斗模板:MIN(CASE WHEN 事件 THEN 时间 END) 把各步压成一行 → 按时间先后逐级比较 → COUNT(CASE WHEN ... THEN 1 END) 数各步人数(无 ELSE 即天然只数"真走到"的)。与 SQL原理_04 漏斗一节完全同源。


🔗 题集索引:LeetCode题集索引 · 算法地图:学习-数据结构与算法MOC