ROW_NUMBER()差值法识别连续日期的本质是:日期与行号同为等差数列,其差值恒定,形成连续段“锚点”;需先过滤达标记录再排序编号做差,否则结果失真。

为什么用 ROW_NUMBER() 差值法能识别连续段
连续日期的本质是:日期本身等差,而行号也等差。当两者都以相同步长递增时,它们的差值会恒定——这个恒定值就是同一连续段的“锚点”。只要数据按日期排序后,对达标记录打上 ROW_NUMBER(),再用日期转成整数(如 TO_DAYS(date) 或 CAST(date AS DATE) 后算差),相同差值的行就属于同一次连续达标序列。
关键前提是:必须先过滤出“达标记录”(比如 score >= 80),再排序、编号、做差。如果在全量表上直接算差,非达标日会打断逻辑,结果完全失真。
MySQL 中实现连续天数统计的完整写法
假设表 t_daily_score 有字段 date(DATE 类型)、score(INT),目标是统计每次连续 ≥80 分的起止日期和天数:
SELECT
MIN(date) AS start_date,
MAX(date) AS end_date,
COUNT(*) AS days
FROM (
SELECT
date,
score,
TO_DAYS(date) - ROW_NUMBER() OVER (ORDER BY date) AS grp
FROM t_daily_score
WHERE score >= 80
) t
GROUP BY grp
ORDER BY start_date;
注意点:
-
TO_DAYS()在 MySQL 中安全可靠;DATEDIFF(date, '1970-01-01')也可,但需确保基准日早于所有数据 - 必须用
WHERE score >= 80提前过滤,不能把条件挪到外层HAVING - 若日期有缺失(如节假日无记录),该方法仍视作“连续”,因只依赖实际存在的记录;若要严格按日历连续,得先补全日期再左连接
PostgreSQL 和 SQL Server 的写法差异
核心思路一致,但日期转整数方式不同:
PostgreSQL:(date - '1970-01-01'::DATE)::INT 或直接 date - ROW_NUMBER() OVER (...)::INTEGER(因为 date - int → date,需改用 date - INTERVAL '1 day' * (ROW_NUMBER() - 1) 不推荐);更稳妥的是用 RANK() OVER (ORDER BY date) 配合 date - (RANK() - 1),但 ROW_NUMBER 更通用。
SQL Server:DATEDIFF(day, '1900-01-01', date) 最常用,注意别用 GETDATE() 当基准——会导致结果随执行时间漂移。
共性陷阱:
- 所有数据库中,
ORDER BY必须明确写在OVER里,且与业务顺序一致(通常是date升序) - 若存在同一天多条记录,需先去重或聚合(如取当日最高分),否则
ROW_NUMBER()会为同日分配不同序号,拆散本应连续的组
查最长连续天数时容易漏掉的边界情况
直接 MAX(COUNT(*)) 会报错(聚合嵌套不合法),必须两层查询:
SELECT MAX(days) AS max_consecutive_days FROM ( SELECT COUNT(*) AS days FROM (...) GROUP BY grp ) t;
但更危险的是:当整个表没有一条达标记录时,内层子查询返回空集,外层 MAX() 返回 NULL,而非 0。业务系统若没处理 NULL,可能引发空指针或展示异常。
真正健壮的做法是加 COALESCE(MAX(days), 0),并确认上游是否允许“0 天”作为有效结果。另外,如果达标阈值本身是动态参数(如来自配置表),千万别写死在 SQL 里——应通过应用层传参或 CTE 注入,避免硬编码导致后期难维护。










