用lag()获取用户前序订单日期,再用date_diff()计算间隔天数,筛选≤7天的记录即可识别业务定义的连续购买;需统一时区、避免用row_number()替代时间分组。

怎么用 LAG() 和 DATE_DIFF() 判断用户是否连续购买
连续购买不是指“隔天买”,而是指在业务定义的时间窗口内(比如 7 天内)有两次及以上购买。关键在于:先算出每个用户相邻两次购买的间隔,再筛选出满足条件的记录。
常见错误是直接用 WHERE order_date = DATE_SUB(prev_date, INTERVAL 1 DAY) —— 这只抓“严格隔天”,漏掉大量真实连续行为(比如周一买、周四又买,中间没下单但仍在活跃周期内)。
- 用
LAG(order_date)按user_id分组、按order_date排序,取出上一笔订单日期 - 用
DATE_DIFF(CURRENT_DATE(), prev_order_date, DAY)(BigQuery)或DATEDIFF('day', prev_order_date, order_date)(Trino/Spark)算间隔天数 - 注意时区:所有
order_date字段必须统一为业务时区(比如Asia/Shanghai),否则跨日计算会错位
为什么 ROW_NUMBER() 不能代替 RANK() 做连续会话分组
营销场景里常要把用户多次购买打包成“一次连续行为”(比如 7 天内三次下单算一个高意向会话)。这时候要按时间切片分组,而不是单纯排序。
错误做法是给每笔订单打 ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date),然后试图用序号差反推间隔——序号只反映顺序,不携带时间信息,一旦中间缺单,序号差就失真。
- 正确思路是用“前序订单时间 + 容忍窗口”作为锚点,比如:
MAX(order_date) OVER (PARTITION BY user_id ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) - 更稳的做法是用递归或自连接生成会话 ID,但性能差;生产环境推荐用
ARRAY_AGG+UNNEST预聚合最近 N 笔订单,再逐条判断 - MySQL 8.0+ 不支持
ARRAY_AGG,得用变量模拟,但并发下容易状态错乱
GROUPS BETWEEN 窗口帧在连续行为统计中的实际限制
想统计“每个用户最近 3 笔订单的平均金额”,直觉会写 AVG(amount) OVER (PARTITION BY user_id ORDER BY order_date GROUPS BETWEEN 2 PRECEDING AND CURRENT ROW)。但这个语法只有少数引擎支持(Trino、Doris),PostgreSQL 和 BigQuery 都不认。
现象是报错:Unsupported window frame type: GROUPS 或直接忽略帧定义,退化成 ROWS 行为。
- 兼容写法是改用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW,但前提是数据没有重复时间戳;如果有同秒多单,ROWS可能漏掉本该纳入的订单 - 补救办法:加一个唯一序列字段(如
order_id)作为二级排序键,确保ORDER BY order_date, order_id下每行位置确定 - BigQuery 中可临时用
ARRAY_AGG(amount ORDER BY order_date LIMIT 3)再UNNEST计算,但数据量大时内存易爆
如何避免 OVER(PARTITION BY user_id) 导致的倾斜和超时
头部用户可能有上万笔订单,PARTITION BY user_id 会让整个窗口计算卡在一个 task 上,尤其在 Spark 或 Flink 中容易 OOM 或触发 speculative execution。
不是所有连续行为分析都需要精确到每一笔——营销策略往往只需要“是否发生过连续购买”,而非“发生了几次”。可以提前剪枝。
- 先用
WHERE order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)限定时间范围,别全量扫历史表 - 对超高频用户(比如
COUNT(*) > 500)单独抽样处理,或用布隆过滤器快速跳过明显不满足间隔条件的用户 - 如果只要布尔结果(是/否连续),用两遍聚合更稳:第一遍算每个用户的最小/最大购买日期和订单数,第二遍只对满足基础条件的用户跑窗口逻辑
连续行为的“连续”是业务定义的,不是 SQL 自带的。窗口函数只是工具,真正难的是把运营目标翻译成可计算的时间约束和分组逻辑。别让 PARTITION BY 成为默认选项,先想清楚这一列值的分布和边界。










