不能直接用 group by 统计流失客户,因为流失是动态行为,group by 会丢失时间序列信息;需用窗口函数保留行粒度并计算lag、滚动均值等实时指标,结合多维加权与本地时区转换实现精准预警。

为什么不能直接用 GROUP BY 统计流失客户
因为流失是动态行为,不是静态快照——比如某客户上月活跃、本月沉默、下月又回归,GROUP BY 会把这三次行为压成一条聚合记录,丢失时间序列关键信息。窗口函数能保留原始行粒度,同时引入前后上下文,这才是预警模型的基础。
典型错误是写 SELECT user_id, COUNT(*) FROM events GROUP BY user_id HAVING MAX(event_time) ,这只能识别“已流失”,无法预警“即将流失”。真正要捕获的是:最近一次行为距今多少天、之前是否连续多日活跃、活跃强度是否断崖下跌。
用 ROW_NUMBER() 和 LAG() 构建用户行为时序特征
ROW_NUMBER() 按用户+时间排序,能标记每条行为是该用户的第几次操作;LAG() 则可取出上一次行为时间,算出间隔天数——这是判断沉默期的核心。
- 必须加
PARTITION BY user_id ORDER BY event_time DESC,否则跨用户错位 -
LAG(event_time, 1)默认返回 NULL(首条记录无前驱),需用COALESCE处理,例如:COALESCE(DATEDIFF('day', LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time), event_time), 999) - 别用
LAG(event_time, 7)直接跳七天前——窗口函数不支持“跳过中间行”,它只取逻辑上第 N 行前的值,不是时间偏移
用 AVG() OVER + CASE WHEN 计算近期活跃衰减率
单纯看“最后一次登录距今 X 天”容易误判:高频用户停 3 天就算风险,低频用户停 30 天才需关注。得结合历史节奏建模,而 AVG() OVER 能在滑动窗口内算出基准活跃频率。
示例:统计每个用户过去 30 天内平均每日事件数,再和最近 7 天对比:
AVG(CASE WHEN event_time >= CURRENT_DATE - INTERVAL '30 days' THEN 1 ELSE 0 END) OVER (PARTITION BY user_id ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
注意点:
-
ROWS BETWEEN比RANGE BETWEEN更可控——后者按值范围(如时间)可能包含意外行,前者按物理行数 - 若数据稀疏(用户行为间隔大),
RANGE可能拉进无关记录,导致均值失真 - 分母用
COUNT(*)而非固定 30,因用户实际覆盖天数可能不足
组合条件输出风险等级,避免硬阈值陷阱
没有万能阈值。同一 last_active_days = 15,对电商用户可能是高危,对 SaaS 工具用户可能只是正常周末休眠。得用多维信号加权:
- 沉默天数(
LAG计算)权重 40% - 近 7 天事件数 / 近 30 天均值
- 最近 3 次行为间隔方差 > 5 → 权重 20%(说明节奏紊乱)
- 是否完成关键路径(如支付成功)→ 权重 10%,用
MAX(CASE WHEN event_type = 'pay_success' THEN 1 ELSE 0 END) OVER (PARTITION BY user_id)
最终用 CASE WHEN 分档:WHEN score >= 0.8 THEN 'high_risk'。关键是所有子指标必须基于窗口函数实时计算,而非离线聚合表——否则新行为进来后,风险评分无法秒级更新。
最容易被忽略的是时区处理:event_time 若存为 UTC,但业务规则按本地时间判定“最近 7 天”,必须先用 AT TIME ZONE 转换,否则凌晨入库的数据会错位一天。











