LAG()计算沉默天数必须加PARTITION BY user_id,否则跨用户取值错误;ORDER BY升序用于算“距上次多久”,需COALESCE处理首行NULL;ROWS BETWEEN比RANGE更安全;应结合用户历史节奏用滚动活跃基线和分位分层判断风险。

LAG() 计算沉默天数必须加 PARTITION BY user_id
直接写 LAG(event_time) 会跨用户取值,比如用户 A 的最后行为被当成用户 B 的“上一次”,结果全错。必须明确分区:LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time)。ORDER BY 用升序还是降序,取决于你想算“距上次多久”还是“距下次多久”——预警流失通常用升序,这样 LAG 取的是前一次行为时间,再用 DATEDIFF('day', LAG(...), event_time) 就是本次与上次的间隔。
边界问题不能忽略:首条记录没有前驱,LAG() 返回 NULL,直接参与 DATEDIFF 会整行变 NULL。要用 COALESCE(DATEDIFF(...), 999) 或类似兜底,否则后续 WHERE 判断失效。
- 别用
LAG(event_time, 7)想跳到 7 天前——它只跳逻辑行数,不是时间偏移 - 如果 event_time 有重复(同一秒多事件),ORDER BY 后建议加唯一键如
event_id避免排序不稳定 - 某些引擎(如 Redshift)要求 ORDER BY 字段非 NULL,空时间戳会导致该行被窗口函数跳过
用 AVG() OVER 算滚动活跃基线,别硬套固定天数
单纯看“距今 15 天没登录”不靠谱:高频用户停 3 天就算风险,低频用户停 30 天才需关注。得结合用户自身历史节奏。用 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) 算过去 30 天平均日行为次数,但注意分母不能硬写 30——实际覆盖天数可能不足,应基于 COUNT(*) 动态算。
ROWS BETWEEN 比 RANGE BETWEEN 更安全:后者按时间值范围拉数据,若用户行为稀疏(比如隔两周才一次),RANGE 可能把两个月前的记录也卷进来,均值失真。
- MySQL 8.0+ 和 BigQuery 支持 ROWS BETWEEN;Hive 3.1 之前不支持,得改用 RANGE 或拆成两步聚合
- 订单类行为要先去重:
DISTINCT order_id,否则同一单多次上报会虚高活跃度 - 分期付款场景下,累计 LTV 应按
payment_date而非下单时间,否则曲线滞后
组合多个窗口指标输出风险等级,避开单一阈值陷阱
没有万能阈值。同一 last_active_days = 15,对电商用户可能是高危,对 SaaS 工具用户只是周末休。推荐用 NTILE(5) 或 PERCENT_RANK() 做分位分层,再叠加业务规则:比如 “沉默天数在本用户群前 20% 且近 7 天活跃强度比 30 天均值下降超 70%” 才标高风险。
别用 HAVING MAX(event_time) < ... 这种 GROUP BY 写法——它只识别“已流失”,丢掉所有中间状态,无法预警“即将流失”。窗口函数保留行粒度,才能捕获断崖下跌、连续沉默等动态信号。
-
PERCENT_RANK()比NTILE()更适分布不均的数据,它反映真实位置而非强制等分 - 跨时区数据必须提前统一转成 UTC 或业务本地时区,否则窗口内时间差计算全乱
- 首访时间要用
MIN(event_time) OVER (PARTITION BY user_id)预计算,别在 JOIN 后再算,否则明细行丢失
子查询关联时 alias 不是可选项,是语法硬性要求
写 SELECT * FROM (SELECT user_id FROM activity WHERE ...) 会直接报错:subquery in FROM must have an alias。这不是兼容性问题,是 SQL 标准强制规定。必须写成 (SELECT user_id FROM activity WHERE ...) t,哪怕只是单字母别名。
更隐蔽的坑是 NOT IN 遇 NULL:子查询里只要有一条 customer_id 是 NULL,整个 NOT IN 返回空集。换成 NOT EXISTS 才稳——尤其在 CRM 关联投诉、登录、订单多张表时,NULL 值几乎不可避免。
- 大表上慎用关联子查询,性能差;优先改用 LEFT JOIN + 聚合
- 复杂多条件分析建议用
WITH,提升可读性和执行计划稳定性 - 窗口函数嵌套时务必加别名,否则 PostgreSQL/MySQL 8.0+ 报
column "xxx" must appear in the GROUP BY clause
窗口函数做流失分析的关键,不在函数本身多难,而在时间上下文是否对齐、分区是否干净、NULL 是否兜底——这些细节一漏,结果就从预警变成误报。

















