直接用MAX()和MIN()算不出重叠时长,因为其结果是全局最值,会错误包含非公共覆盖段;真实重叠需所有活动共同覆盖的交集区间,须通过事件扫描法(起始/结束事件排序+计数)或窗口函数逐段识别并求和。

为什么直接用 MAX() 和 MIN() 算不出重叠时长?
因为促销活动时间经常交叉(比如“满减”和“折扣券”同时生效),单纯取全局最大结束时间和最小开始时间,会把非重叠段也计入。真实重叠必须是「所有活动都覆盖的公共区间」,本质是求多个区间交集的总长度。
关键思路:把每个活动拆成「起始事件」和「结束事件」,按时间排序后扫描计数——当计数达到活动总数时,进入重叠;计数回落时,退出重叠。
- 需提前知道参与计算的活动总数(例如筛选出当前用户持有的 3 张有效券)
- 事件表必须包含类型标识(
type字段,值为'start'或'end') - 同一毫秒内多个事件需约定顺序:
start排在end前,避免瞬时计数错误
用窗口函数实现逐段重叠判断(PostgreSQL / MySQL 8.0+)
不用写存储过程,靠 LAG() 和累计计数就能定位每一段重叠区间起点和终点。
假设活动表叫 promo_events,字段为 id, start_time, end_time:
WITH events AS (
SELECT start_time AS ts, 1 AS delta FROM promo_events
UNION ALL
SELECT end_time AS ts, -1 AS delta FROM promo_events
),
ordered AS (
SELECT ts, delta,
SUM(delta) OVER (ORDER BY ts, delta DESC) AS active_count
FROM events
),
segments AS (
SELECT ts AS segment_start,
LEAD(ts) OVER (ORDER BY ts) AS segment_end,
active_count
FROM ordered
)
SELECT SUM(GREATEST(0, segment_end - segment_start)) AS overlap_seconds
FROM segments
WHERE active_count = (SELECT COUNT(*) FROM promo_events);注意:delta DESC 确保同一时间点先处理 start(delta=1),再处理 end(delta=-1)。
MySQL 5.7 怎么绕过窗口函数限制?
用变量模拟累计计数,但必须严格控制执行顺序——ORDER BY 不可省,且不能有并行优化干扰。
- 先建临时事件表并显式排序:
CREATE TEMPORARY TABLE event_seq AS SELECT ts, delta FROM (...) ORDER BY ts, delta DESC - 再用
@cnt := @cnt + delta变量累加,每次更新前记录上一行ts - 变量初始化必须在同一个查询中完成:
SELECT @cnt := 0放在FROM子句里 - 结果可能因优化器重排而错乱,生产环境建议升级到 8.0+
重叠计算容易被忽略的边界细节
时间精度不一致会导致漏算或重复:数据库用 TIMESTAMP,但活动配置可能只精确到天,而实际生效按秒级校验。
- 统一转成 UTC 时间戳再比较,避免时区转换污染
-
start_time和end_time是否包含端点?多数业务按「左闭右开」处理([start, end)),此时重叠条件应为segment_end > segment_start而非>= - 跨日促销(如 23:59 到次日 00:05)在按天分表场景下,需确保查询覆盖完整时间范围,不能只查单日分区
真正麻烦的不是算法,而是时间语义在各层(前端输入、DB 存储、应用逻辑)是否对齐。没对齐,再准的 SQL 也算不准。

















