窗口函数不能直接判断预订重叠,但通过LAG()获取前序check_out并与当前check_in比较可实现;需按room_id分组、check_in排序,且须处理端点相接和跨库差异。

窗口函数能直接解决预订重叠判断吗?不能,但它是关键拼图
窗口函数本身不判断重叠,它只是帮你把同一房间的预订按时间排序、带上前后行数据,让重叠逻辑可计算。真正判断是否重叠,得靠 LAG() 或 LEAD() 拿到相邻记录的 check_in 和 check_out,再做比较。比如:当前预订的 check_in 小于上一条的 check_out,就说明重叠了。
- 必须先按
room_id分组、按check_in排序,否则LAG()拿到的不是前一次入住 -
LAG(check_out)返回的是上一条记录的离店时间,不是当前记录的——这点容易看反 - 重叠判断要覆盖「端点相接」场景(如 A 离店是 2024-05-01,B 入住也是 2024-05-01),通常用
check_in < LAG(check_out) OVER (...),而不是<=,除非业务允许当天退房当天入住
怎么写一个能标出所有重叠预订的查询?
核心思路是:对每个房间,用 LAG() 获取前一次的 check_out,再和当前 check_in 比较;同时用 ROW_NUMBER() 标记原始顺序,方便定位问题记录。
SELECT
id,
room_id,
check_in,
check_out,
LAG(check_out) OVER (PARTITION BY room_id ORDER BY check_in, check_out) AS prev_check_out,
CASE
WHEN check_in < LAG(check_out) OVER (PARTITION BY room_id ORDER BY check_in, check_out)
THEN 'OVERLAP'
ELSE 'OK'
END AS status
FROM bookings;
-
PARTITION BY room_id是必须的,漏掉就会跨房间乱比 -
ORDER BY check_in, check_out中加check_out是为避免同一天多次入住时排序不稳定 - 如果某条记录的
prev_check_out为NULL(即首条预订),CASE会自动进ELSE分支,无需额外处理
如何找出「连续重叠链」而不仅是单对重叠?
单次 LAG() 只能发现和前一条的重叠,但现实中可能出现 A-B 重叠、B-C 重叠、C-D 重叠,形成一条四条记录的重叠链。这时要用递归 CTE 或累积标记——更实用的是用窗口函数模拟「重叠传播」:一旦某条被标为重叠,后续只要 check_in < MAX(prev_check_out) OVER (...) 就继续标重叠。
- 先用
LAG()找出所有直接前驱重叠点 - 再用
MAX()窗口函数滚动计算「已知最晚离店时间」,即MAX(COALESCE(prev_check_out, check_out)) OVER (PARTITION BY room_id ORDER BY check_in) - 当前
check_in < 滚动最晚离店时间,就属于同一重叠簇 - 注意:滚动计算必须严格按
check_in升序,否则逻辑崩塌
MySQL 8.0+ 和 PostgreSQL 的细微差异要注意什么?
两者都支持标准窗口函数,但默认行为有坑:MySQL 对 ORDER BY 子句在窗口定义中要求更严,PostgreSQL 允许更灵活的排序表达式。另外,MySQL 的 LAG() 在遇到 NULL 时不会报错,但 PostgreSQL 同样安全。
- MySQL 8.0 必须显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW才能确保MAX()窗口是累积的;PostgreSQL 默认就是累积模式 - PostgreSQL 支持
IGNORE NULLS修饰符(如LAG(check_out) IGNORE NULLS),MySQL 不支持,遇到中间有空值需提前过滤或用子查询处理 - 两个引擎对
TIMESTAMP和DATE类型的比较行为一致,但若字段含时分秒,务必确认是否需要截断——重叠判断通常只关心日期,可用DATE(check_in)统一
重叠逻辑看似简单,真正上线时最容易栽在排序稳定性、端点是否包含、以及跨数据库的窗口帧默认行为上。别跳过测试用例:插入两条同房间同一天的预订,再插一条前一天入住后天离店的,看是否全被正确标记。

















