LAG必须配合PARTITION BY和ORDER BY才能正确计算时间差:缺PARTITION BY会跨用户混算,缺ORDER BY结果不可控;时间字段须为TIMESTAMP/DATETIME类型,需用数据库特定函数计算差值;首行NULL须在子查询或CTE中处理,不可在WHERE中直接过滤。

LAG必须配合PARTITION BY和ORDER BY才能算对时间差
不加PARTITION BY会跨用户/任务混算,不加ORDER BY结果完全不可控。比如查用户复购间隔,漏写PARTITION BY user_id,A用户的最后一单可能接在B用户的第一单后面,算出几小时甚至几天的“间隔”,毫无业务意义。
排序字段必须带业务含义:优先用event_time或order_time,别用id(除非确认严格递增且无删改)。时间字段重复时(如同秒下单),务必加二级排序,例如ORDER BY order_time, order_id,否则窗口行为不稳定。
-
PARTITION BY task_id确保只在同一个流程实例内比较 -
ORDER BY event_time, id防重复、保序 - 字段类型必须是
TIMESTAMP或DATETIME,字符串存时间会报错或返回0
时间差不能直接相减,得按数据库选函数
写current_time - LAG(time)在多数库会报错或返回0。必须调用对应数据库的时间差函数,且参数顺序、单位、大小写都敏感。
PostgreSQL支持直接减法但返回interval,要秒数得套EXTRACT(EPOCH FROM ...);MySQL和SQL Server根本不认减法,硬写就失败。
- PostgreSQL:
EXTRACT(EPOCH FROM (order_time - LAG(order_time) OVER (...))) / 60→ 得到分钟(含小数) - MySQL:
TIMESTAMPDIFF(SECOND, LAG(order_time) OVER (...), order_time)→ 单位必须显式,顺序不能反 - SQL Server:
DATEDIFF(second, LAG(order_time) OVER (...), order_time)→ 第一个参数是单位 - BigQuery:
TIMESTAMP_DIFF(order_time, LAG(order_time) OVER (...), SECOND)→ 第三个参数大小写敏感
首行NULL必须主动处理,WHERE里不能直接用LAG
LAG()对每个分组第一行天然返回NULL,直接参与计算会让整行结果变NULL。更麻烦的是,你不能在WHERE里写LAG(...) IS NOT NULL——窗口函数执行晚于WHERE,语法直接报错。
正确做法是先用子查询或CTE把差值列算出来,再在外层过滤。MySQL还要求子查询必须加别名,否则报Every derived table must have its own alias。
- 用
COALESCE(LAG(order_time), order_time)让首行差值为0(即自身减自身) - 想排除首次购买?外层
WHERE prev_time IS NOT NULL,不是在窗口里判断 - 子查询必须带别名,例如
SELECT * FROM (SELECT ..., LAG(...) AS prev_t FROM t) AS tmp WHERE prev_t IS NOT NULL
阈值设定和空值含义要结合业务判断
算出来的时间差是数字,但“多大才算断点”或“负值是否合理”全看业务逻辑。大量NULL说明某任务只有一条记录(如流程中断);负值基本等于数据乱序或人工补录。
传感器每5秒一条,设阈值10秒可容忍延迟;订单系统若出现order_time相同,间隔为0,要明确是否允许、是否需标记。
- 别用
COALESCE(LEAD(), NOW())填未完结阶段的空值——会把平均耗时拉高几倍 - 用
ABS((curr - prev) / NULLIF(prev, 0)) > 0.3代替绝对差值,防金额量级干扰 - 如果要统计“24小时内复购率”,必须先算出间隔列,再在外层加
WHERE interval_sec
真正容易被忽略的,是时间字段的精度和时区。MySQL的DATETIME只到秒级,PostgreSQL默认带微秒,BigQuery用纳秒——跨库迁移时差值可能差几个数量级。还有,所有时间字段必须统一时区,否则凌晨3点和下午3点算出来可能是12小时,而不是业务预期的断点。

















