孤岛与区间合并问题中,孤岛指连续相邻的数值或时间序列段(如连续登录日期),间隙是其间的空缺;区间合并则将重叠或紧邻区间压缩为单个区间。核心解法是用ROW_NUMBER()生成序号,以原始字段减序号得到恒定差值作为孤岛分组标识,再聚合统计;需先去重、确保日期类型,并适配不同数据库语法。

什么是孤岛与区间合并问题
孤岛(Islands)指连续的、相邻的数值或时间序列段,比如连续登录的日期;间隙(Gaps)是这些段之间的空缺。区间合并则是把重叠或紧邻的区间(如 [1,3], [2,5])压缩成一个([1,5])。这类问题用传统 GROUP BY 很难处理,因为分组边界不固定——必须依赖行间顺序和差值规律。
用 ROW_NUMBER() + 差值法识别孤岛
核心思路:对有序数据打序号,再用原始字段减去序号。同一孤岛内,这个差值恒定;不同孤岛差值不同。这是最稳定、兼容性最好的方法。
假设表 logins 有字段 login_date DATE,要找连续登录的“孤岛”:
SELECT
MIN(login_date) AS island_start,
MAX(login_date) AS island_end,
COUNT(*) AS days
FROM (
SELECT
login_date,
DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (ORDER BY login_date) DAY) AS grp
FROM logins
) t
GROUP BY grp;-
ROW_NUMBER()按日期升序编号,从 1 开始 -
DATE_SUB(... INTERVAL ... DAY)确保差值是日期类型,避免隐式转换错误 - 若数据含重复日期,先
DISTINCT或用DENSE_RANK(),否则会把同一天拆成多个“伪孤岛” - PostgreSQL 要写成
login_date - ROW_NUMBER() OVER (...)::INT;SQL Server 用DATEADD(day, -ROW_NUMBER()..., login_date)
用 LAG() + 累积标记做区间合并
当输入是带起止边界的区间(如 start_time/end_time),且需合并重叠或相邻区间时,LAG() 判断前一行是否可延续,再用累积条件生成分组键。
关键不是直接 GROUP BY,而是构造一个不会被跨区间打断的标识列:
SELECT
MIN(start_time) AS merged_start,
MAX(end_time) AS merged_end
FROM (
SELECT *,
SUM(is_new_group) OVER (ORDER BY start_time, end_time) AS grp_id
FROM (
SELECT *,
CASE
WHEN start_time <= LAG(end_time) OVER (ORDER BY start_time, end_time)
THEN 0 ELSE 1
END AS is_new_group
FROM intervals
) t1
) t2
GROUP BY grp_id;-
LAG(end_time)取上一行合并后的右边界,判断当前区间是否能接上 - 排序必须严格:先按
start_time,再按end_time,否则LAG()可能漏掉本该合并的区间 - 若允许“相邻即合并”(如
[1,3]和[4,5]→[1,5]),条件改为start_time - MySQL 8.0+、PostgreSQL、SQL Server 都支持;但 SQLite 不支持窗口函数中的
SUM() OVER (ORDER BY ...)嵌套,得拆成两层 CTE
为什么不能只靠 MIN()/MAX() 和普通分组
因为孤岛/合并的本质是**状态依赖**:当前行是否属于前一个组,取决于前一行的计算结果。普通聚合没有行序上下文,强行 GROUP BY FLOOR((id-1)/3) 这类静态分组只会切错边界。
容易踩的坑包括:
- 忽略
ORDER BY在窗口函数里的必要性:ROW_NUMBER()不写ORDER BY会报错或返回不确定结果 - 用
RANK()替代ROW_NUMBER():遇到重复值时跳号,差值不再恒定,孤岛会被意外切断 - 在时区敏感场景下直接减整数:如
created_at - 1在 PostgreSQL 中是减 1 秒,不是减 1 天,必须显式用INTERVAL或make_interval() - 大数据量时没加索引:
ORDER BY login_date若无索引,ROW_NUMBER()扫全表极慢
真正麻烦的从来不是语法,而是差值语义是否对齐业务定义——比如“连续”是指日历连续,还是工作日连续,或是按事件发生顺序连续。这点没法靠函数自动推断,得人来确认。

















