group by login_date无法计算留存率,因它统计的是每日dau而非首日新增用户;正确做法是先用group by user_id求min(login_date)得first_login,再按此分组并left join回访记录,日期条件须放on中。

直接用 GROUP BY 对 login_date 统计,根本得不到留存率——它只算出每日活跃用户数,分母不是“新增”,而是“当天登录的所有人”。真正要的是:以每个用户的首次行为日为基准,再看他们后续是否回访。这一步绕不开识别 first_login,GROUP BY 本身做不到。
为什么 GROUP BY login_date 会算错分母
常见错误写法:SELECT login_date, COUNT(DISTINCT user_id) FROM user_logins GROUP BY login_date。这统计的是每日 DAU,不是某日新增用户。比如用户 A 在 6 月 1 日、6 月 3 日、6 月 5 日都登录过,这条语句会在三天里各计一次,但他在留存分析中只应属于「6 月 1 日 cohort」的分母,且只算一次。
- 分母必须是「首次登录发生在当天」的用户集合,不是「当天有登录」的用户集合
-
GROUP BY login_date没法区分一个用户是第几次登录,更无法锁定其首次行为日 - 若表中存在脏数据(如
login_date IS NULL),MIN()可能返回NULL,导致整个分母污染
正确做法:先用子查询或 CTE 构造首日用户池
核心是把「每个用户的首次登录日」提前算出来,再按这个日期分组。MySQL 和 Hive 都支持,兼容性好:
- 用
GROUP BY user_id算每人MIN(login_date),得到first_login - 加
WHERE first_login BETWEEN '2026-09-01' AND '2026-09-30'限定 cohort 范围 - 必须补
AND login_date IS NOT NULL,避免空值干扰MIN()结果 - 若需按渠道分组(如
channel),GROUP BY必须包含channel, user_id,否则跨渠道混入分母
示例片段:
WITH first_login AS (<br> SELECT user_id, channel, MIN(login_date) AS first_date<br> FROM user_logins<br> WHERE login_date IS NOT NULL<br> GROUP BY channel, user_id<br>)
LEFT JOIN 回访记录时,日期偏移必须写在 ON 条件里
不是 WHERE d.login_date = DATE_ADD(f.first_date, INTERVAL 1 DAY),而是必须放在 ON 子句中,否则 LEFT JOIN 会退化成 INNER JOIN,漏掉没回访的用户:
- 错误:
LEFT JOIN daily_login d ON f.user_id = d.user_id WHERE d.login_date = ...——WHERE会过滤掉d为NULL的行 - 正确:
LEFT JOIN daily_login d ON f.user_id = d.user_id AND d.channel = f.channel AND d.login_date = DATE_ADD(f.first_date, INTERVAL 1 DAY) - Hive/MaxCompute 中注意函数差异:
DATE_ADD(f.first_date, 1, 'dd'),不是INTERVAL语法 - 多日留存(1/7/30)别写多个
LEFT JOIN,改用单次JOIN+ 多个CASE WHEN,避免别名冲突和条件遗漏
COUNT(DISTINCT) 在大数据量下容易拖慢查询
如果只是要留存率数值(非精确去重人数),可用整型聚合替代:
- 用
SUM(CASE WHEN d1.user_id IS NOT NULL THEN 1 ELSE 0 END)替代COUNT(DISTINCT d1.user_id) - 分母也改用
COUNT(*)(因first_loginCTE 中已按user_id去重) - 最终计算写成:
ROUND(SUM(...)*100.0 / COUNT(*), 2),避免隐式整除截断 - 若数据量极大,建议预计算每日活跃宽表(含
user_id,login_date,is_first),再做 JOIN,别实时扫原始日志表
最易被忽略的一点:所有时间字段必须统一转为 DATE 类型处理,不能直接比对带时分秒的 DATETIME,否则 DATE_ADD 或 TO_DATE 偏移可能错位。










