SQL如何合并用户连续日期记录获取各时间段起止日期
连续登录日期段合并实现方案
这个需求属于SQL经典的**Gaps and Islands(间断与区间)**问题,核心是识别同一用户下日期的断点,给连续的日期段打上统一分组标记后聚合即可,不需要写复杂的循环或者存储过程,用窗口函数就能高效实现。
核心实现思路
- 对每个用户的登录记录按日期升序排序,生成连续行号
- 用每条记录的登录日期减去对应行号的天数,生成分组标记:同一连续日期段内的所有记录,计算出的分组标记值完全相同,日期间断时标记值会发生跳变
- 按用户账号+分组标记聚合,取分组内最小的
date_start作为段起始、最大的date_end作为段结束,输出结果即可
可直接运行的代码示例
以下写法兼容MySQL 8.0+、PostgreSQL、Hive、Spark SQL等所有支持标准窗口函数的数据库:
WITH login_with_groupflag AS ( SELECT login, date_start, date_end, DATE_SUB( date_start, INTERVAL ROW_NUMBER() OVER (PARTITION BY login ORDER BY date_start) DAY ) AS group_flag FROM your_login_table -- 替换成你的实际表名 ) SELECT login, MIN(date_start) AS date_start, MAX(date_end) AS date_end FROM login_with_groupflag GROUP BY login, group_flag ORDER BY date_start;
针对你提供的样例数据,上述代码运行后会直接返回你期望的结果:
| login | date_start | date_end | |-------|------------|------------| | user1 | 2022-01-01 | 2022-01-03 | | user2 | 2022-01-05 | 2022-01-06 | | user1 | 2022-02-24 | 2022-02-26 |
兼容场景说明
- 如果你用的是不支持窗口函数的MySQL 5.x版本,可以用用户变量模拟行号计算,核心逻辑和上述方案完全一致,参考写法如下:
SELECT login, MIN(date_start) AS date_start, MAX(date_end) AS date_end FROM ( SELECT login, date_start, date_end, @row_num := IF(@current_login = login, @row_num + 1, 1) AS rn, @group_flag := DATE_SUB(date_start, INTERVAL @row_num DAY) AS group_flag, @current_login := login FROM ( SELECT * FROM your_login_table ORDER BY login, date_start ) AS sorted_records, (SELECT @row_num := 0, @current_login := '', @group_flag := '') AS init_vars ) AS records_with_flag GROUP BY login, group_flag ORDER BY date_start;
- 如果你的表中存在跨多日的登录段(即
date_end大于date_start的记录),只需要把分组标记逻辑调整为「判断当前记录的起始日期和上一条记录的结束日期间隔是否超过1天,超过则标记为新分段起点,再通过累计求和生成分组ID」即可,核心思路不变。
内容的提问来源于stack exchange,提问作者zak
相关产品推荐
相关产品推荐

