LAG()需配合OVER子句,正确用法为LAG(purchase_time) OVER (PARTITION BY user_id ORDER BY purchase_time),并加二级排序防重复;时间差计算须适配数据库类型;过滤首次购买用WHERE prev_purchase_time IS NOT NULL;建议建复合索引提升性能。

用 LAG() 获取上一次购买时间
核心是把同一用户的购买记录按时间排序后,把前一行的 purchase_time 拉下来跟当前行对比。LAG() 必须配合 OVER 子句使用,否则会报错 "window function requires an OVER clause"。常见错误是忘记按用户分组或没指定排序,导致拉错时间。
-
LAG(purchase_time) OVER (PARTITION BY user_id ORDER BY purchase_time)—— 正确:按用户分组、按时间升序 -
LAG(purchase_time) OVER (ORDER BY purchase_time)—— 错误:所有用户混在一起排,A 用户的上一笔可能是 B 用户的 - 如果
purchase_time有重复(同用户同秒下单),建议加二级排序,比如ORDER BY purchase_time, order_id,避免窗口行为不稳定
计算时间差要注意数据库类型
不同数据库对时间相减的返回值类型不一致,直接写 current_time - prev_time 可能报错或结果不符合预期。
- PostgreSQL:支持直接相减,返回
interval,可用EXTRACT(EPOCH FROM ...)转成秒数 - MySQL:用
TIMESTAMPDIFF(SECOND, prev_time, current_time),单位必须显式指定,TIMESTAMPDIFF不接受负值参数顺序 - SQL Server:用
DATEDIFF(second, prev_time, current_time),注意参数顺序是(unit, start, end) - BigQuery:用
TIMESTAMP_DIFF(current_time, prev_time, SECOND),单位是第三个参数,大小写敏感
过滤出有效间隔(排除首次购买)
LAG() 对每个分组的第一行返回 NULL,这是正常行为,但后续计算差值时会导致整行变成 NULL。如果只想看“第二次及以后购买”的间隔,必须显式过滤。
- 在
WHERE中加prev_purchase_time IS NOT NULL—— 最简单直接 - 别用
HAVING,因为这不是聚合查询;也别试图在SELECT里用CASE WHEN把NULL替换成 0,这会掩盖“首次购买”这个业务事实 - 如果业务需要标记首次购买,建议保留原始行,用
ROW_NUMBER() OVER (...) = 1单独判断,而不是依赖LAG()是否为NULL
性能和索引建议
当用户订单量大(比如百万级),PARTITION BY user_id ORDER BY purchase_time 的窗口计算可能变慢,尤其没索引时。
- 确保表上有复合索引:
CREATE INDEX idx_user_time ON orders(user_id, purchase_time) - 避免在
LAG()外层再套复杂表达式(比如嵌套函数处理时间差),先算出基础差值列,再做二次加工 - 如果只关心“最近两次购买间隔”,可先用子查询限制每人最多取 2 条(
ROW_NUMBER() ),再算差值,比全量跑窗口快得多
实际执行时最容易被忽略的是时间字段的精度和时区。比如 purchase_time 是 TIMESTAMP WITHOUT TIME ZONE,但应用写入时没统一时区,算出来的间隔可能跨天却显示为几小时。查之前先确认字段类型和数据写入逻辑。

















