复购间隔天数本质是同一用户两次购买的时间差,须用LEAD()按user_id分区、order_time升序计算,跨用户或错序会导致结果失真;最后一单LEAD返回NULL属正常,需显式过滤或处理。

复购间隔天数的本质是“同一用户两次购买之间的时间差”
直接用 LEAD() 是最自然的解法:对每个用户的订单按时间排序,取下一笔订单的下单时间,减去当前订单时间即可。关键不在于函数多难,而在于是否正确分区、排序和处理空值。
必须按 user_id 分区 + order_time 升序排序
漏掉 PARTITION BY user_id 会导致跨用户计算,比如用户 A 的最后一单和用户 B 的第一单相减,结果完全失真;排序不用 ASC 或写成 DESC 会让 LEAD() 拿到错误的“下一笔”。
常见错误写法:LEAD(order_time) OVER (ORDER BY order_time) —— 缺少分区,全局排序,毫无业务意义。
正确结构应为:
LEAD(order_time) OVER ( PARTITION BY user_id ORDER BY order_time ASC )
注意:order_time 必须是可比较的时间类型(如 TIMESTAMP 或 DATE),字符串格式如 '2023-05-01' 在多数数据库中也能隐式转换,但若含时分秒且精度不一致(如 '2023-05-01 10:30:00' vs '2023-05-01'),可能影响排序稳定性。
LEAD() 返回 NULL 时需显式过滤或处理
每个用户的最后一笔订单,LEAD() 必然返回 NULL,直接参与减法会得到 NULL 结果。这不是 bug,是预期行为——复购间隔只存在于“有下一笔”的场景。
实操建议:
- 若只想看有复购的记录,
WHERE next_order_time IS NOT NULL最干净 - 若需保留所有订单并标记“首次购买”,可用
COALESCE(DATEDIFF(next_order_time, order_time), -1),但注意-1是业务约定,不是通用解法 - 避免写成
DATEDIFF(LEAD(...), order_time)后再WHERE ... IS NOT NULL—— 部分数据库(如旧版 MySQL)不支持在WHERE中直接引用窗口函数别名,得套一层子查询
不同数据库的 DATEDIFF 和时间减法语法差异要盯紧
LEAD() 行为基本一致,但算天数的方式五花八门:
- MySQL:
DATEDIFF(LEAD(order_time) OVER (...), order_time) - PostgreSQL:
(LEAD(order_time) OVER (...) - order_time)::INTEGER(返回天数整数) - SQL Server:
DATEDIFF(day, order_time, LEAD(order_time) OVER (...)) - BigQuery:
DATE_DIFF(LEAD(date_col) OVER (...), date_col, DAY),注意它要求输入是DATE类型,传TIMESTAMP会报错
最容易被忽略的一点:有些数据库(如 Hive)不支持在窗口函数里直接做日期运算,必须先用 LEAD() 提取时间字段,再在外层计算差值——否则语法报错或结果异常。










