




正确计算留存率应先汇总分子分母再相除,而非直接avg(留存率);分母需用LEFT JOIN保证完整cohort;DATE(event_time)必须显式转换以避免时分秒导致漏数据。
很多人写完每日留存率后,顺手加个 AVG(retention_rate) 算“平均留存”,结果偏差高达十几个百分点。这不是 SQL 写错了,是统计逻辑错了。
AVG() 把它们全当 1 来算权重2025-12-01 有 1000 新用户,次日留存 60%;2025-12-02 只有 10 个新用户,次日留存 90%。直接 AVG(60, 90) = 75%,但真实加权均值是 (600 + 9) / (1000 + 10) ≈ 60.3%
AVG() 套比率字段计算留存率时,分母必须是完整首日用户集合;分子是其中在第 N 日有行为的子集。用错连接方式,等于主动丢掉分母的一部分。
INNER JOIN:只保留“当天来了 + 次日也来了”的用户 → 分母变小,结果虚高LEFT JOIN + COUNT(DISTINCT c.user_id):确保分母始终是原始 cohort,哪怕没人回访,分母也不缩水SELECT COUNT(DISTINCT e.user_id)/COUNT(DISTINCT c.user_id) 却用了 INNER JOIN → 表面看语法通,实际逻辑崩了很多日志表的 event_time 是 DATETIME 或 TIMESTAMP 类型,直接 WHERE event_time = '2025-12-01' 几乎查不到数据,因为默认匹配到 00:00:00。
DATE(event_time) = '2025-12-01' 或 event_time >= '
2025-12-01' AND event_time
toDate(event_time),比 toStartOfDay() 更稳同一个“次日留存”,A 同事算的是 day1 / day0,B 同事算的是 day2 / day1,俩数字都对,但放一起就是灾难。
-- 本口径:起始日为 day0,次日留存 = day1 回访用户数 / day0 首次登录用户数
cohort_date AS '2025-12-01', retention_day AS 1, target_date AS '2025-12-02'
真正卡住人的从来不是 JOIN 或 DATEDIFF 怎么写,而是“我们到底在算什么”。留存量子一动,整个运营归因、AB 实验结论、预算分配逻辑都会跟着偏——所以每次上线新留存脚本前,先拿 3 个已知样本手工验算一遍分母、分子、日期对齐是否一致。