必须用lag(login_date) over (partition by user_id order by login_date)取前次登录日,配合timestampdiff(day, prev_login, login_date) = 1判断次日留存,分母须限定为观察期内新用户,并前置清洗脏数据。

LAG必须配合PARTITION BY user_id和ORDER BY login_date
单独写 LAG(login_date) 不会报错,但结果完全不可用——它会把所有用户的登录时间混在一起排序,张三的下一次登录可能被填成李四的日期。MySQL 8.0+ 在严格模式下甚至直接拒绝执行。真正起作用的只有带明确分组和排序的组合:LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date)。如果 login_date 有秒级重复(比如埋点并发),建议追加主键或 id 到 ORDER BY 后,避免排序不稳定。
用DATEDIFF而非DATE_ADD判断“是否次日回来”
LAG() 只负责取值,不负责逻辑判断。常见错误是写成 WHERE next_login IS NOT NULL,这统计的是“有后续行为”,不是“次日就回来”。正确做法是计算自然天数差:
- MySQL:用
TIMESTAMPDIFF(DAY, login_date, LAG(login_date) OVER (...)) = 1 - 别用
DATE_ADD(login_date, INTERVAL 1 DAY) = prev_login——跨月时失效(如 1 月 31 日 +1 天 ≠ 2 月 1 日) - 也别用字符串截取或
DAYOFYEAR(),它们无法处理跨年、跨周偏移
分母必须限定为“首次登录落在观察期内”的用户
留存率的分母不是某天所有活跃用户,而是该周期内新用户。漏掉“首次”前提,老用户会被误计入,导致留存率被稀释。实操中要先锚定每个用户的首次登录日:
一款AI工具,主要用于在主代理响应前,并行运行Kimi K2.5和GPT 5.3 Codex,注入双方观点以增强认知多样性,适合需要提升相关任务效率的用户。
- 用
MIN(login_date) OVER (PARTITION BY user_id)算出first_login - 再在外层筛选:
WHERE first_login BETWEEN '2026-08-01' AND '2026-08-07' - 原始数据含脏数据(如
'0000-00-00'或 NULL)时,务必前置过滤:AND login_date >= '2026-01-01' AND login_date IS NOT NULL
LAG默认返回NULL,别让它悄悄影响聚合结果
当用户只登录一次,LAG(login_date) 返回 NULL。如果后续直接参与 COUNT(*) 或除法,会导致分母变小、比率虚高。不同数据库对 NULL 的处理不一致:PostgreSQL 中 day_diff = 1 对 NULL 返回 NULL(等效 false),而某些 Hive 版本会跳过整行。稳妥做法是显式控制:
- MySQL 用
IFNULL(LAG(login_date), '1970-01-01') - BigQuery 可用第三个参数设默认值:
LAG(login_date, 1, '1970-01-01') - 更推荐统一用
COALESCE(TIMESTAMPDIFF(...), -999),再用CASE WHEN day_diff IN (1, 7) THEN ...控制逻辑分支
真正难的不是写对 LAG(),而是意识到它只解决“取值”问题;间隔判断、分母界定、NULL 安全、日期精度——每个环节都得手动补位,少一个,结果就偏了。










