ltv必须基于生命周期已结束的用户计算,而非全量用户平均:先按注册cohort分组,筛选连续90天无行为的用户,对其每人总付费求avg;未付费用户需明确口径剔除,避免拉低估值。

用 AVG() 和 SUM() 分层计算 LTV,别直接套公式
直接对用户总消费求 AVG() 得到的不是真实 LTV——它混入了未完成生命周期的用户(比如刚注册 7 天就流失),会严重低估。LTV 必须基于「已自然结束生命周期」的用户计算,或对未结束者做合理截断。
实操建议:
- 先定义「生命周期结束」:常见做法是用户连续 90 天无任何付费/活跃行为,对应 SQL 中用
MAX(pay_time)或MAX(active_time)判断最后行为时间,再和当前日期比对 - 只纳入生命周期已结束的用户:例如
WHERE DATEDIFF(CURDATE(), MAX(pay_time)) > 90(MySQL)或WHERE CURRENT_DATE - MAX(pay_time) > INTERVAL '90 days'(PostgreSQL) - 对每个这类用户,先算其总付费金额(
SUM(amount)),再对这批人取平均:AVG(user_total),这才是稳健的 LTV 估算
用 GROUP BY + 窗口函数识别留存 cohort,避免漏掉新老用户分层偏差
不同注册月份的用户,LTV 天然不同。如果全量聚合,2022 年注册的老用户拉高均值,2024 年新用户表现被掩盖。必须按 cohort(如注册年月)分组,再分别算 LTV。
实操建议:
- 用
DATE_FORMAT(reg_time, '%Y-%m')(MySQL)或TO_CHAR(reg_time, 'YYYY-MM')(PostgreSQL)提取注册 cohort - 对每个 cohort,先关联该用户所有后续付费记录,
GROUP BY cohort, user_id计算每人总消费 - 再嵌套一层:
SELECT cohort, AVG(user_ltv) FROM (...) GROUP BY cohort,得到各期用户的平均 LTV 趋势 - 注意:窗口函数如
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY pay_time)可辅助判断首次/末次付费,但 LTV 本身不依赖窗口,别为用而用
警惕 NULL 和 0 用户导致 AVG() 偏差
很多业务中,大量用户注册后从未付费。若把他们 SUM(amount) 视为 0 并参与 AVG(),LTV 会被显著拉低——这不是模型问题,是定义错位:LTV 通常指「付费用户」的价值,不是「全部注册用户」的价值。
实操建议:
- 明确业务口径:是「All Users LTV」还是「Paying Users LTV」?前者需包含 0,后者必须
WHERE SUM(amount) > 0过滤 - 检查
amount字段是否含NULL:聚合前加COALESCE(amount, 0),否则SUM()会跳过整行(取决于 SQL 模式) - 避免用
COUNT(*)作分母:应该用COUNT(DISTINCT user_id),且这个 user_id 必须来自已确认生命周期结束的子集
在时序不完整场景下,用 PERCENTILE_CONT() 替代 AVG() 更抗干扰
当部分用户生命周期被截断(比如数据只到 2024-06,但有些用户 2024-05 才注册),他们的 user_ltv 是不完整的,拉低 AVG() 同时又无法剔除——这时均值敏感度太高,中位数或分位数更稳。
实操建议:
- PostgreSQL 支持
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY user_ltv)直接算中位数 LTV - BigQuery 用
APPROX_QUANTILES(user_ltv, 100)[OFFSET(50)];MySQL 8.0+ 需用变量模拟,较麻烦,不如换用MEDIAN()(若版本支持) - 分位数不能替代 cohort 分析:仍要先按注册月分组,再对每组算
PERCENTILE_CONT,否则混合不同成熟度用户没意义
实际跑的时候,最常卡在「怎么定义生命周期结束」和「要不要剔除未付费用户」这两个点上。业务方往往口头说“看付费用户”,但 SQL 里忘了加 HAVING SUM(amount) > 0,结果一跑出来 LTV 是个极小值,回头查半天才发现是分母混进了几万零消费账号。











