min/max仅返回日期全局起止,无法识别连续区间;需用row_number与日期差构造分组键,再按键聚合取每段极值,且须依数据库适配日期转整数写法并建立索引优化性能。

MIN 和 MAX 不能直接识别“连续日期区间”
很多人误以为 MIN(date_col) 和 MAX(date_col) 能自动合并出连续的日期段——实际它们只返回整个范围的起止,完全忽略中间是否断开。比如数据是 '2024-01-01'、'2024-01-02'、'2024-01-05'、'2024-01-06',MIN/MAX 会返回 '2024-01-01' 到 '2024-01-06',掩盖了中间缺失的 '2024-01-03' 和 '2024-01-04'。
用 ROW_NUMBER 配合日期差构造“连续组标识”
核心思路是:对有序日期打序号,再用日期本身减去序号。同一连续段内,这个差值恒定;断点处差值突变。这是识别连续区间的通用模式。
实操建议:
- 先按日期升序排列并用
ROW_NUMBER() OVER (ORDER BY date_col)生成序号 - 将
date_col转为整数天数(如 PostgreSQL 用date_col::int,MySQL 用TO_DAYS(date_col),SQL Server 用DATEDIFF(day, '1900-01-01', date_col)) - 计算
date_as_int - row_num作为分组键 - 在外层按该键
GROUP BY,再对每组取MIN(date_col)和MAX(date_col)
示例(PostgreSQL):
SELECT MIN(date_col) AS start_date,
MAX(date_col) AS end_date
FROM (
SELECT date_col,
date_col::int - ROW_NUMBER() OVER (ORDER BY date_col) AS grp
FROM events
) t
GROUP BY grp
ORDER BY start_date;
不同数据库的日期转整数写法差异大,别硬套
同一个逻辑在各数据库落地时,date_col 转整数的方式必须匹配其类型系统,否则结果错乱或报错:
- PostgreSQL:
date_col::int或EXTRACT(EPOCH FROM date_col)::int / 86400 - MySQL:
TO_DAYS(date_col)(注意它从公元0年算起,但只要同库内一致即可) - SQL Server:
DATEDIFF(day, '1900-01-01', date_col)(基准日固定,避免隐式转换) - SQLite:
julianday(date_col),需用ROUND(julianday(date_col))取整
漏掉类型转换或选错基准日,会导致 grp 值漂移,分组全乱。
大数据量下性能关键在索引和分区裁剪
这个查询本质是全表排序 + 窗口函数 + 分组聚合,没有索引支撑时极易变慢:
- 必须在
date_col上建升序索引(如CREATE INDEX idx_events_date ON events(date_col)) - 如果业务上只查最近 3 个月,务必加
WHERE date_col >= '2024-01-01',让优化器能跳过旧分区 - 避免在子查询中对
date_col做函数操作(如DATE(date_col)),否则索引失效
连续区间识别本身不复杂,但一旦日期列没索引,或数据跨多年没分区,ROW_NUMBER 就得扫几百万行——这时候 MIN/MAX 反而是最省事的,只是结果不准。











