left join做留存漏斗时必须在on中同时约束user_id和日期偏移,否则分子会变成累计活跃数导致结果超100%;分母须为首次行为子集,不能直接用日活;多日留存应单次join配合case when,而非多次left join;left join无法保证行为顺序,需结合窗口函数或专用函数处理路径逻辑。

LEFT JOIN必须在ON里同时约束user_id和日期偏移
算留存漏斗时,只写 ON n.user_id = d.user_id 是错的——它会把用户后续所有登录都连进来,导致分子变成累计活跃数,结果可能超100%。真正有效的次日留存JOIN,必须带日期条件:ON n.user_id = d.user_id AND d.login_date = DATE_ADD(n.first_login, INTERVAL 1 DAY)。
不同引擎日期函数写法不一致,容易写错:
- MySQL:
DATE_ADD(first_login, INTERVAL N DAY) - Hive/SparkSQL:
DATE_ADD(first_login, N)(注意参数顺序相反) - ClickHouse:
plusDays(first_login, N)或toDate(addDays(toDate(first_login), N))
漏掉日期约束,等于放弃时间维度控制,漏斗就不是“第N日是否回访”,而是“有没有回访过”。
分母必须是首次行为子集,不能直接按login_date分组
用 SELECT login_date, COUNT(DISTINCT user_id) FROM user_logins GROUP BY login_date 得到的是日活(DAU),不是新增 cohort。漏斗分母要求是“首次行为发生在当天”的用户集合。
正确做法有两种:
- 用窗口函数打标:
MIN(login_date) OVER (PARTITION BY user_id),外层WHERE first_login = '2026-06-01' - 用子查询聚合:
SELECT user_id, MIN(login_date) AS first_login FROM user_logins GROUP BY user_id
别忘了加 AND login_date IS NOT NULL 过滤脏数据,否则 MIN() 可能返回 NULL,污染整个分组结果。
多日留存别写多个LEFT JOIN,改用单次JOIN + CASE WHEN
为次日、7日、30日各写一次 LEFT JOIN,不仅SQL冗长,还极易出错:别名冲突、日期偏移写成29、漏掉某个 ON 条件,都会让结果失真。
推荐写法是只连一次后续行为表,靠多个 CASE WHEN 判断匹配:
SELECT n.first_login, COUNT(DISTINCT n.user_id) AS cohort_size, COUNT(DISTINCT CASE WHEN d.login_date = DATE_ADD(n.first_login, INTERVAL 1 DAY) THEN n.user_id END) AS day1_retained, COUNT(DISTINCT CASE WHEN d.login_date = DATE_ADD(n.first_login, INTERVAL 7 DAY) THEN n.user_id END) AS day7_retained, COUNT(DISTINCT CASE WHEN d.login_date = DATE_ADD(n.first_login, INTERVAL 30 DAY) THEN n.user_id END) AS day30_retained FROM first_cohort n LEFT JOIN user_logins d ON n.user_id = d.user_id
这样逻辑清晰、可维护性强,且避免了JOIN爆炸风险。
LEFT JOIN漏斗不解决步骤顺序问题,需配合窗口函数或专用函数
LEFT JOIN 本质是集合匹配,它只能回答“用户在第N日有没有登录”,但无法判断「浏览→加购→下单」是否按序发生、间隔是否合理。一旦业务要求路径严格性(比如加购必须在浏览后30分钟内),仅靠 LEFT JOIN 就会高估转化率。
此时必须引入时间序列建模:
- 用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time)标记行为序号 - 用
LAG(event_type, 1) OVER (PARTITION BY user_id ORDER BY event_time)获取上一步类型 - ClickHouse 用户可直接用
windowFunnel()函数做高效路径匹配
漏斗分析真正的难点不在连接,而在如何定义“有效路径”——这个定义一旦模糊,LEFT JOIN 再规范也救不了结果失真。










