订阅更新

SQL 如何计算次日、7日和30日留存率?

面试问 133 约 3 分钟读完

本文目录
  1. 面试怎么回答
  2. 第一步:先定义新增用户 cohort
  3. 第二步:明确什么叫“回来”
  4. 第三步:用日期差映射 D1、D7、D30
  5. 第四步:分母必须固定在对应 cohort
  6. 一个典型场景

面试怎么回答

我会先定义新增用户 cohort 和业务时区,再把回访行为去重到用户日粒度。然后将行为日期与 cohort 日期做日期差,分别统计 D1、D7、D30 的去重回访用户数,分母固定为对应 cohort 的新增人数。还要区分“第 N 天”和“N 天内”,对尚未成熟的日期保留 NULL,并用人数、比率和用户样本三层校验,防止 Join 放大分母。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

产品说“看一下最近一周的次日、7日和30日留存”,很多人马上就开始写 SQL。

可真正决定结果的,往往不是窗口函数,而是:新增用户是哪一批、什么行为算回来、日期差按哪个时区计算。

留存率的核心不是比较两个日期的 DAU,而是固定一批新增用户,观察同一批人在第 N 天是否再次出现。


第一步:先定义新增用户 cohort

cohort 可以理解为同一天进入系统的一批用户。最常见的是注册日期,也可能是首次付费日期、首次启动日期或第一次完成核心行为的日期。

建 cohort 时要先对 user_id 去重,并明确业务时区。一个用户重复注册、账号合并或跨时区上报,都可能让新增人数变大。

第二步:明确什么叫“回来”

回访行为不能默认等于任意日志。登录、启动 App、观看内容和完成支付,代表的是不同留存口径。

同一个用户在同一天触发多次行为,只能计为一个回访用户。因此通常先把行为表整理成 user_id + event_date 的日粒度集合,再与 cohort 关联。

留存率 cohort 计算链路

第三步:用日期差映射 D1、D7、D30

把 event_date 与 cohort_date 做日期差:差值为 1 是次日留存,差值为 7 是 7 日留存,差值为 30 是 30 日留存。

这里最容易混淆“第7天留存”和“7天内留存”。前者只看第7个自然日,后者看一个区间,SQL 条件和业务含义完全不同。

COUNT(DISTINCT CASE WHEN day_diff = 7 THEN user_id END)
/
COUNT(DISTINCT user_id)

真正落地时,分子和分母最好分别聚合后再相除,避免行为明细 Join 后把 cohort 分母重复放大。

第四步:分母必须固定在对应 cohort

某天的 D7 留存分母,是七天前那批新增用户,不是某天 DAU,也不是七天内所有新增用户。

输出通常以 cohort_date 为行,以 D1、D7、D30 为列。越新的 cohort 还没有走到第30天,对应位置应该保持 NULL 或“未成熟”,不能当成 0,否则最近日期的留存曲线会被人为拉低。


一个典型场景

一张看板显示次日留存突然上涨,但新增用户数和产品版本都没有明显变化。继续核对才发现,SQL 用“次日活跃用户数 ÷ 当日新增用户数”,分子并没有限制为当日新增的那批人。

老用户越活跃,这个所谓的留存率就越高,甚至可能超过 100%。

修正方式不是换一个函数,而是先生成新增 cohort,再按 user_id 关联后续行为,并分别验证 cohort 人数、回访人数和抽样明细。

留存是一种 cohort 分析,用户身份必须贯穿分子和分母。

刷题进度保存在当前浏览器