用窗口函数识别连续月份比GROUP BY + COUNT更可靠,因后者仅统计次数或去重月数,无法判断月份是否连贯;而基于“年月整数−行号”恒定值分组可精准捕获任意长度的连续区间。

用窗口函数识别连续月份比GROUP BY + COUNT更可靠
单纯靠 GROUP BY user_id 和 COUNT(*) 无法判断“连续三个月”——它只能告诉你某用户总共买了几次,或在多少个不同月份买过,但月份是否连着完全看不出来。比如用户在1月、3月、5月各买一次,COUNT(DISTINCT month) 是3,却不是连续的。
真正可行的思路是:先按用户和月份去重(避免单月多笔干扰),再对每个用户的月份排序,用「当前月份 - 排名」构造等差标识。连续月份的这个差值会恒定,从而能分组计数。
MySQL 8.0+ 实现连续三个月购买的完整写法
假设表为 orders,含字段 user_id、order_date(DATE类型):
SELECT user_id
FROM (
SELECT user_id,
YEAR(order_date) * 100 + MONTH(order_date) AS ym,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY YEAR(order_date) * 100 + MONTH(order_date)) AS rn
FROM (SELECT DISTINCT user_id, DATE_FORMAT(order_date, '%Y-%m-01') AS order_date FROM orders) t0
) t1
GROUP BY user_id, (ym - rn)
HAVING COUNT(*) >= 3;
关键点:
-
DATE_FORMAT(order_date, '%Y-%m-01')统一归到每月1号,消除日影响 -
YEAR(...) * 100 + MONTH(...)把年月转成可计算整数(如202403),方便做算术 -
ym - rn是核心:对连续月份,这个值不变(例如202401−1=202400,202402−2=202400,202403−3=202400) - 外层
GROUP BY user_id, (ym - rn)才能真正捕获每一段连续区间
PostgreSQL 或 SQL Server 怎么适配
逻辑一致,只需调整日期处理函数:
- PostgreSQL:用
EXTRACT(YEAR FROM order_date) * 100 + EXTRACT(MONTH FROM order_date)替代ym计算 - SQL Server:用
YEAR(order_date) * 100 + MONTH(order_date)即可,但注意需先DISTINCT去重月粒度 - 所有版本都必须先
DISTINCT user_id, year_month,否则同月多单会让ROW_NUMBER()膨胀,破坏连续性判断
为什么不能用LAG/LEAD直接比上月是否存在
可以,但维护成本高且易漏场景。例如用 LAG(ym, 1) 和 LAG(ym, 2) 判断当前行是否为连续第三个月,会要求用户至少有3条记录才能进入结果集——如果某用户只在2月、3月、4月有订单,但查询时只取了最近3个月数据,而2月记录被过滤掉,就永远匹配不到。
相比之下,基于 ym - rn 的分组法不依赖绝对时间窗口,只要数据存在,任意长度的连续段都能被识别出来。真正的难点其实在前期清洗:确保没有时区错乱、order_date 为空或非法值,这些都会让 ROW_NUMBER() 错位,导致连续性误判。










