0Pricing
Coding Interview Prep · 课时

第 N 天留存与滚动留存

了解经典留存、滚动留存和有界留存定义之间的区别。

第 N 天留存与滚动留存 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。

为什么留存有多种定义

面试官很少只会说“计算留存”。更有针对性的追问是:是哪一种留存?根据定义不同,同一份数据会得出非常不同的数字。

您必须掌握的三种定义是:第 N 天(经典)留存、滚动(无界)留存,以及有界窗口留存。了解它们的区别,并询问业务需要哪一种,本身就是这道题要考查的能力。

第 N 天(经典)留存

第 N 天留存要回答的是:用户是否在首次操作后的恰好第 N 天处于活跃状态?第 1 天、第 7 天和第 30 天是移动应用中最经典的指标。

关键字是恰好。如果用户在第 6 天和第 8 天活跃,但第 7 天不活跃,那么按照经典定义,他不属于第 7 天留存用户。这种精确性使计数更加严格,也使曲线更加曲折。

计算天数差

第 N 天留存的核心,是计算队列起始日与每个活动日期之间的天数。在 Postgres 中,两个日期相减会直接得到整数天数。

其他 SQL 方言中的写法包括:SQL Server 使用 DATEDIFF(day, start, d),MySQL 使用 DATEDIFF(d, start)。请说明所使用的方言;这个概念(天偏移量)是相同的。

WITH cohort AS (
  SELECT user_id, MIN(event_at::date) AS day0
  FROM events GROUP BY user_id
),
act AS (
  SELECT DISTINCT user_id, event_at::date AS day
  FROM events
)
SELECT c.user_id, (a.day - c.day0) AS day_n
FROM cohort c
JOIN act a ON a.user_id = c.user_id;

第 7 天留存查询

要得到第 7 天留存率,请统计天偏移量等于 7 的去重用户数,再除以队列规模。使用条件聚合,这样分子和分母都可以来自一次扫描。

= 7 这个相等条件(而不是 >= 7)是经典留存的标志。换成不等式会悄悄改变定义。

WITH dn AS (
  SELECT c.user_id, (a.day - c.day0) AS day_n
  FROM cohort c JOIN act a ON a.user_id = c.user_id
)
SELECT
  COUNT(DISTINCT CASE WHEN day_n = 7 THEN user_id END) AS d7_retained,
  COUNT(DISTINCT user_id)                               AS cohort_size,
  ROUND(100.0 * COUNT(DISTINCT CASE WHEN day_n = 7 THEN user_id END)
        / NULLIF(COUNT(DISTINCT user_id), 0), 1)         AS d7_pct
FROM dn;

滚动(无界)留存

第 N 天的滚动留存提出了一个更宽松的问题:用户是否在第 N 天或之后的任意一天处于活跃状态?如果用户在第 7 天、第 20 天,或更晚的任何时间回来,都会被计为第 7 天留存用户。

这种定义会产生更平滑、更高的曲线,因此常用于衡量长期粘性。其标志性变化是:针对最大活动日,将 = N 改为 >= N。

使用 MAX 天数计算滚动留存

计算滚动留存的一种清晰方法是:先找出每位用户的最后活跃日偏移量(MAX),然后当该最大值 >= N 时,就将用户计为第 N 天的滚动留存用户。

执行 MAX 后每位用户只对应一行,因此计数很简单。这也清楚表明滚动留存具有单调性:如果用户在第 30 天仍被留存,那么在所有更小的 N 上也都会被留存。

WITH last_day AS (
  SELECT c.user_id, MAX(a.day - c.day0) AS max_day_n
  FROM cohort c JOIN act a ON a.user_id = c.user_id
  GROUP BY c.user_id
)
SELECT
  COUNT(*)                                          AS cohort_size,
  COUNT(*) FILTER (WHERE max_day_n >= 7)            AS rolling_d7,
  ROUND(100.0 * COUNT(*) FILTER (WHERE max_day_n >= 7)
        / NULLIF(COUNT(*), 0), 1)                    AS rolling_d7_pct
FROM last_day;

有界窗口留存

中间方案是有界留存:用户在第 N 天附近的某个窗口内至少活跃过一次,例如将第 5 天到第 9 天作为“第 1 周”指标。它允许用户不在某个确切日期活跃,同时又比完全滚动的定义更少地进行平滑处理。

这是最符合实际业务的定义,因为真实的使用往往具有突发性。查询会在天偏移量上使用 BETWEEN。

SELECT
  COUNT(DISTINCT CASE WHEN day_n BETWEEN 5 AND 9
                      THEN user_id END) AS week1_retained,
  COUNT(DISTINCT user_id)               AS cohort_size
FROM dn;

同一用户的三种定义

让我们通过一个具体例子理解区别。某用户在第 0 天开始,此后只在第 9 天活跃过。

  • 经典第 7 天:不属于留存用户(没有在恰好第 7 天活动)。
  • 滚动第 7 天:属于留存用户(最大天数为第 9 天,>= 7)。
  • 有界第 5–9 天第 1 周:属于留存用户(第 9 天落在该窗口内)。

同一份数据,三种答案。在面试中,请像这样讲述一个例子,以证明您理解的是语义,而不只是语法。

周期粒度:日、周与月

“第 N 天”可以泛化为第 N 个周期。对于按月使用的面向企业的产品,按天计算的留存会充满噪声;您应按月分组,并询问第 N 个月的留存。其机制完全相同,只有截断粒度发生变化。

请选择与产品自然使用节奏相匹配的粒度,并明确说明这一点。面向消费者的移动应用适合按天,使用节奏较慢的企业工具则适合按周或按月。

-- weekly grain: offset in whole weeks
SELECT
  c.user_id,
  FLOOR((a.day - c.day0) / 7) AS week_n
FROM cohort c
JOIN act a ON a.user_id = c.user_id;

生存者偏差与成熟度陷阱

一个细致的高级面试考点是:不要报告一个只有 10 天历史的队列的第 30 天留存。它还没有获得在第 30 天活跃的机会,因此该值人为地变成了 0,并不代表真实的低留存。

报告第 N 天留存时,只纳入存续时间 >= N 的队列。请根据 CURRENT_DATE - day0 >= N 进行筛选。忘记这一点会让近期队列看起来糟糕得不真实。

WITH cohort AS (
  SELECT user_id, MIN(event_at::date) AS day0
  FROM events GROUP BY user_id
)
SELECT *
FROM cohort
WHERE (CURRENT_DATE - day0) >= 30;  -- mature enough for Day-30

明确说出您选择的定义

面试中最好的回答不是直接写查询,而是反问一句:“您需要经典的第 N 天留存、滚动留存,还是有界窗口留存?自然的周期是什么?”

然后说明取舍:经典留存严格,适合衡量确切日期的产品触发点;滚动留存会高估短期留存,但能捕捉长期粘性;有界留存则是更符合实际的折中方案。证明您是在有意识地选择指标,这正是本课的核心。

快速检查

某用户的首次操作发生在第 0 天;此后唯一一次活动发生在第 12 天。按照每种定义,在第 7 天时该用户是否属于留存用户?

回顾:留存定义

第 N 天留存与滚动留存的要点:

  • 经典第 N 天留存:恰好在第 N 天活跃(偏移量 = N)— 严格,曲线曲折。
  • 滚动留存:在第 N 天或之后活跃(MAX offset >= N)— 更平滑、单调,用于衡量粘性。
  • 有界留存:在某个窗口内活跃(BETWEEN)— 符合实际的折中方案。
  • 将按天的定义泛化为与产品使用节奏相匹配的任意周期粒度。
  • 只有在报告第 N 天留存时纳入成熟队列(存续时间 >= N),才能避免生存者偏差陷阱,并且始终询问业务所说的究竟是哪种定义。

常见问题解答

「第 N 天留存与滚动留存」课时是免费的吗?

是的 — 「第 N 天留存与滚动留存」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。

「第 N 天留存与滚动留存」这节课中我会学到什么?

了解经典留存、滚动留存和有界留存定义之间的区别。 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Coding Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「第 N 天留存与滚动留存」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Coding Interview Prep 课中编写并运行代码吗?

能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 按首次操作定义用户群组
  2. 构建留存矩阵
  3. 第 N 天留存与滚动留存
  4. 流失与回归查询
← 返回 Coding Interview Prep