ROW_NUMBER差值法原理是:连续日期与对应行号的差值恒定,该差值作为连续组标识;需先排序、去重、清洗空值及非法日期,并适配不同数据库的日期运算语法。

ROW_NUMBER差值法识别连续日期的原理是什么
核心在于:连续日期的「日期本身」和「按顺序编号的行号」之差是恒定的。比如 2024-01-01、2024-01-02、2024-01-03,对应 ROW_NUMBER() 是 1、2、3,那么 date - ROW_NUMBER()(转成天数差)结果都是 2023-12-31。这个恒定值就是“连续组”的标识。
注意:
- 必须先按日期排序再计算
ROW_NUMBER(),否则差值无意义 - 日期类型需支持算术运算(如 PostgreSQL/SQL Server 支持
date - integer;MySQL 要用DATE_SUB(date, INTERVAL rn DAY)) - 若数据含重复日期(同天多次打卡),得先去重,否则
ROW_NUMBER()会把同一天拆成多行,破坏连续性
PostgreSQL/SQL Server 中的标准写法
以用户打卡表 t_checkin(user_id, check_date) 为例,查每个用户的最长连续打卡天数:
SELECT user_id, COUNT(*) AS streak_days
FROM (
SELECT
user_id,
check_date,
check_date - ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY check_date) AS grp
FROM (SELECT DISTINCT user_id, check_date FROM t_checkin) t
) t2
GROUP BY user_id, grp
ORDER BY streak_days DESC
LIMIT 1;
关键点:
- 外层
GROUP BY user_id, grp把同一连续段归为一组 -
COUNT(*)就是该段天数 -
DISTINCT必须加,否则同日多次打卡会让ROW_NUMBER()虚高,导致差值错位
MySQL 8.0+ 的适配写法
MySQL 不支持直接 date - integer,得改用 DATE_SUB():
SELECT user_id, COUNT(*) AS streak_days
FROM (
SELECT
user_id,
check_date,
DATE_SUB(check_date, INTERVAL ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY check_date
) DAY) AS grp
FROM (SELECT DISTINCT user_id, check_date FROM t_checkin) t
) t2
GROUP BY user_id, grp
ORDER BY streak_days DESC
LIMIT 1;
常见坑:
- 忘记
INTERVAL ... DAY会导致语法错误或隐式转换异常 - MySQL 5.7 不支持窗口函数,此法完全不可用,必须升级或换方案(如变量模拟)
-
check_date字段若含时间部分(如'2024-01-01 08:30:00'),需先用DATE(check_date)截断,否则同一天不同时间会被判为不同日期
为什么有时连续段被意外截断
最常被忽略的是「日期字段存在空值或非法值」:
-
NULL在ORDER BY中可能排在最前或最后,打乱序列 - 非标准日期(如
'0000-00-00'或字符串格式)在排序或减法中引发隐式转换失败,整组计算崩掉
建议前置清洗:
WHERE check_date IS NOT NULL AND check_date != '0000-00-00' AND check_date REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'- 在应用层确保插入前已标准化日期格式,数据库层面加
CHECK约束更稳妥
实际跑出来段数偏短,十有八九是数据质量没兜住,而不是算法逻辑问题。










