直接GROUP BY日期仅按值分组,不识别顺序与间隔,无法区分连续段与孤立点;必须用ROW_NUMBER()配合日期减序号构造恒定分组键(如DATE_SUB(log_date, INTERVAL rn DAY)),相同键值即为同一连续段。

为什么直接用 GROUP BY 日期会漏掉“连续”这个条件
因为 GROUP BY 只按值分组,不关心顺序或间隔。比如数据里有 2024-01-01、2024-01-02、2024-01-04,GROUP BY date 会分成三组,但你真正想识别的是前两天构成一个连续段(长度 2),第三天是孤立点(长度 1)——这需要先对“连续性”做标记,再分组。
用 ROW_NUMBER() 配合日期差构造连续组 ID
核心思路:对同一用户(或分组维度)按日期排序,用 date - INTERVAL ROW_NUMBER() OVER (...) DAY 得到一个恒定值,该值在连续日期段内相同,就是“连续组 ID”。
实操建议:
- 确保日期字段是
DATE类型(不是DATETIME,否则需先CAST(date AS DATE)) - 排序必须严格按日期升序,且
ROW_NUMBER()的ORDER BY和外层一致 - PostgreSQL/MySQL 8.0+/SQL Server 都支持;MySQL 5.7 不支持窗口函数,得用变量模拟(易出错,不推荐)
示例(MySQL 8.0+):
SELECT
user_id,
MIN(log_date) AS start_date,
MAX(log_date) AS end_date,
COUNT(*) AS days_count
FROM (
SELECT
user_id,
log_date,
DATE_SUB(log_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY log_date) DAY) AS grp
FROM login_log
) t
GROUP BY user_id, grp;遇到 NULL 或重复日期怎么办
NULL 会破坏排序和窗口函数结果,重复日期会让 ROW_NUMBER() 产生非预期偏移(比如同一天两条记录,ROW_NUMBER() 是 1 和 2,但日期差一样,导致被错误归入不同组)。
处理方式:
- 先去重:
DISTINCT user_id, DATE(log_time)(如果原始是 datetime) - 过滤 NULL:
WHERE log_date IS NOT NULL - 若业务允许“同日多次算一次”,聚合时用
COUNT(DISTINCT DATE(log_time))替代COUNT(*)
性能差?检查索引和数据量
窗口函数在大数据量下可能慢,尤其没索引时。关键点:
-
PARTITION BY user_id ORDER BY log_date这个组合最好有联合索引:INDEX(user_id, log_date) - 如果只查最近 30 天,加
WHERE log_date >= '2024-01-01'能大幅减少扫描行数 - 连续段超过几千天时,
DATE_SUB计算本身开销不大,瓶颈通常在排序和临时表生成
真正容易被忽略的是:日期字段是否带时区?跨时区服务器上,CURRENT_DATE 和存储的 UTC 时间混用会导致“看似不连续”。确认所有日期已统一为本地时区或全部转为 UTC 再计算。

















