窗口函数配合LAG、ROW_NUMBER和布尔差分可稳定识别业务中断;先用LAG获取前一行时间并计算时间差,超24小时标记为断点;再通过布尔差分与累计求和生成连续段ID;需处理LAG首行NULL、重复时间戳及正确分区,且业务断点定义须前置明确。

窗口函数本身不直接标记断点,但配合 LAG、ROW_NUMBER 和布尔差分逻辑,能稳定识别时间序列中业务意义上的“中断”——比如用户连续登录中断、传感器数据缺失超过阈值、订单状态跳跃等。
用 LAG + 时间差判断物理断点
真正要检测“断点”,核心是看当前行与上一行的时间间隔是否超出预期。不能只比对日期字段是否相等,得算差值。
- 对时间列(如
event_time)先用LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time)拿到前一行时间 - 再用
EXTRACT(EPOCH FROM (event_time - prev_time)) / 3600(PostgreSQL)或TIMESTAMPDIFF(HOUR, prev_time, event_time)(MySQL)算出小时级间隔 - 间隔 > 24 小时就标记为潜在断点:用
CASE WHEN interval_hours > 24 THEN 1 ELSE 0 END AS is_break - 注意:如果原始时间含毫秒,而业务只关心“天级连续”,记得先
DATE(event_time)再比较,否则同一天内多次事件会误判
用布尔差分生成连续段 ID(GROUPS 编号)
单纯标记断点还不够,多数分析需要把断点前后的记录归为不同组——比如“第1次连续活跃期”“第2次连续活跃期”。这靠布尔差分+累计求和实现。
- 先构造一个布尔标志:例如
is_new_session = CASE WHEN interval_hours > 24 THEN 1 ELSE 0 END - 再用
SUM(is_new_session) OVER (PARTITION BY user_id ORDER BY event_time ROWS UNBOUNDED PRECEDING)得到每个事件所属的“会话编号” - 这个编号天然递增且在断点处跳变,后续可直接
GROUP BY user_id, session_id统计每段的时长、事件数等 - 别用
ROW_NUMBER()或RANK()替代——它们按行计数,无法体现逻辑分组
处理边界与空值:为什么 LAG 第一行总是 NULL?
LAG 对排序后首行返回 NULL 是标准行为,不是 bug。但若直接参与计算(如 event_time - LAG(...)),整行结果会变成 NULL,导致断点漏判。
- 必须显式处理:用
COALESCE(LAG(event_time) OVER (...), event_time)把首行前值设为自身,使差值为 0;或更合理地——用CASE WHEN prev_time IS NULL THEN 0 ELSE ... END跳过首行判断 - 如果数据存在重复时间戳(如批量导入未去重),
ORDER BY event_time可能不稳定,建议补上唯一列:ORDER BY event_time, event_id - 分区键(
PARTITION BY)漏写会导致跨用户混算——比如用户 A 的最后一条和用户 B 的第一条被当成连续,这是线上最常踩的坑
断点分析真正的复杂点不在函数调用,而在“业务断点”的定义本身:24 小时是硬规则?是否要排除周末?传感器采样周期不稳时,该用中位数间隔还是固定阈值?这些必须在写 LAG 之前就想清楚,否则窗口函数只会忠实地放大定义错误。

















