首先,您需要为ts_end 获得一个“候选人”,这是大于开始时间的最短结束时间。这可以通过
select s.id, s.ts_start, (
select min(e.ts_end)
from end_time e
where e.id = s.id
and e.ts_end > s.ts_start
) as ts_end
from start_time s;
或与
select s.id, s.ts_start, min(e.ts_end) as ts_end
from start_time s
left join end_time e
on e.id = s.id
and e.ts_end > s.ts_start
group by s.id, s.ts_start
两个查询都会返回
| id | ts_start | ts_end |
|-----|----------|----------|
| 123 | 01:00 | 02:00 |
| 123 | 03:00 | 07:00 |
| 123 | 05:00 | 07:00 |
| 123 | 09:00 | null |
现在,当ts_start 和ts_end 之间有任何开始时间(表start_time)时,我们需要ts_end 成为null(第二行)。对于第二行ts_end 必须是NULL,因为5:00 的开始时间介于3:00 和7:00 之间。
对于第一个查询,我们可以使用带有NOT EXISTS 条件的HAVING 子句:
select s.id, s.ts_start, (
select min(e.ts_end)
from end_time e
where e.id = s.id
and e.ts_end > s.ts_start
having not exists (
select *
from start_time s2
where s2.id = s.id
and s2.ts_start > s.ts_start
and s2.ts_start < min(e.ts_end)
)
) as ts_end
from start_time s
可以使用CASE 表达式和EXISTS 条件扩展第二个查询:
select s.id, s.ts_start,
case when exists (
select *
from start_time s2
where s2.id = s.id
and s2.ts_start > s.ts_start
and s2.ts_start < min(e.ts_end)
)
then null
else min(e.ts_end)
end as ts_end
from start_time s
left join end_time e
on e.id = s.id
and e.ts_end > s.ts_start
group by s.id, s.ts_start
在 MySQL 8.x 中,您可以改用 LEAD 窗口函数:
select s.id, s.ts_start,
case when min(e.ts_end) > lead(s.ts_start) over (partition by s.id order by s.ts_start)
then null
else min(e.ts_end)
end as ts_end
from start_time s
left join end_time e
on e.id = s.id
and e.ts_end > s.ts_start
group by s.id, s.ts_start
所有三个查询都将返回:
| id | ts_start | ts_end |
|-----|----------|----------|
| 123 | 01:00 | 02:00 |
| 123 | 03:00 | null |
| 123 | 05:00 | 07:00 |
| 123 | 09:00 | null |
演示:https://www.db-fiddle.com/f/6qRaYZKnA7ZYMcTmpZFUwj/0