重叠时间段合并是将有交集或紧邻的时间区间(如(1,5)、(4,8))压缩为不重叠的最小连续区间(如(1,8)),核心是按起始时间排序后,用lag()识别新区间起点,并通过累计求和分组,再取每组min(start_time)和max(end_time)。

什么是“重叠时间段合并”的典型 SQL 场景
这个问题本质是把一组可能互相覆盖或紧邻的 [start_time, end_time] 区间,压缩成不重叠、不交叉的最小连续区间集合。比如原始数据有:(1, 5)、(4, 8)、(10, 12),期望结果是:(1, 8)、(10, 12)。这不是简单去重,而是需要识别“当前行是否能接在上一个合并区间的尾巴后面”。
窗口函数在这里不可替代:必须按时间排序后逐行判断累积状态,而 LAG() 和运行累计逻辑(如 MAX() OVER (ORDER BY ... ROWS UNBOUNDED PRECEDING))是核心。
用 LAG() + 累计最大值标记新区间起点
关键思路是:对每行判断“它是否开始了一个新区间”——即它的 start_time 是否大于前一个已知合并区间的右边界。
- 先按
start_time排序,用LAG(end_time) OVER (ORDER BY start_time)拿到上一行的end_time - 定义“是否为新区间起点”:
CASE WHEN start_time > LAG(end_time) OVER (ORDER BY start_time) THEN 1 ELSE 0 END - 对这个标志做累计求和:
SUM(is_new_group) OVER (ORDER BY start_time),得到每个行所属的组号 - 最后按组号分组,取每组的
MIN(start_time)和MAX(end_time)
注意:首行的 LAG() 返回 NULL,需用 COALESCE(LAG(...), start_time - 1) 避免 start_time > NULL 判定为 TRUE(否则首行会错判为新组)。
PostgreSQL / MySQL 8.0+ / SQL Server 的写法差异点
- PostgreSQL 和 SQL Server 支持
ROWS UNBOUNDED PRECEDING,累计逻辑稳定;MySQL 8.0+ 同样支持,但旧版不支持窗口函数,必须用变量模拟(不推荐)
- 所有数据库中,
ORDER BY 必须明确包含唯一键(如 id)防并列排序不确定性,否则同 start_time 的多行可能导致组号错乱
- 如果存在
start_time = end_time 或负区间,需提前 WHERE start_time 过滤,否则 <code>MAX(end_time) 可能失真
ROWS UNBOUNDED PRECEDING,累计逻辑稳定;MySQL 8.0+ 同样支持,但旧版不支持窗口函数,必须用变量模拟(不推荐)ORDER BY 必须明确包含唯一键(如 id)防并列排序不确定性,否则同 start_time 的多行可能导致组号错乱start_time = end_time 或负区间,需提前 WHERE start_time 过滤,否则 <code>MAX(end_time) 可能失真
示例片段(以 PostgreSQL 为例):
SELECT MIN(start_time) AS merged_start,
MAX(end_time) AS merged_end
FROM (
SELECT start_time,
end_time,
SUM(is_new) OVER (ORDER BY start_time, id) AS grp
FROM (
SELECT start_time,
end_time,
id,
CASE WHEN start_time > COALESCE(LAG(end_time) OVER (ORDER BY start_time, id), '-infinity'::timestamp)
THEN 1 ELSE 0 END AS is_new
FROM events
) t1
) t2
GROUP BY grp;
容易被忽略的边界情况
- 时间类型精度不一致(如
datetime vs timestamp with time zone)会导致 LAG() 比较隐式转换失败或偏差
- “紧邻区间”是否合并?例如
(1, 5) 和 (5, 8):若需求要合并,条件得写成 start_time > LAG(end_time) → 不合并;start_time > LAG(end_time) + INTERVAL '1 second' → 合并,需按业务定义“重叠”语义
- 空值处理:任何
start_time 或 end_time 为 NULL 会让整行退出合并逻辑,务必前置清洗
- 性能:该方案需两次排序(子查询嵌套),大数据量时确保
(start_time, id) 有联合索引
datetime vs timestamp with time zone)会导致 LAG() 比较隐式转换失败或偏差(1, 5) 和 (5, 8):若需求要合并,条件得写成 start_time > LAG(end_time) → 不合并;start_time > LAG(end_time) + INTERVAL '1 second' → 合并,需按业务定义“重叠”语义start_time 或 end_time 为 NULL 会让整行退出合并逻辑,务必前置清洗(start_time, id) 有联合索引真正卡住人的往往不是语法,而是没想清楚“什么算重叠”以及“第一行怎么安全初始化”。先跑通单字段数字区间,再套时间类型,比直接怼 timestamp 少踩一半坑。











