我有两个表,一个表保存事件的开始时间,第二个表保存事件的结束时间,我想将两者合并在一起
然而,挑战在于,每个开始事件并不总是有相应的结束事件,如果是的话,我希望输出为NULL。这可能吗?
编辑:每个ID代表一个人,每天可以有多个事件开始和停止。对于每个事件,我只希望将单个“正确”的结束时间加入开始时间(如果存在)。当前没有个人事件级别标识符。
例如:
表1:开始时间
id ts_start
123 01:00
123 03:00
123 05:00
123 09:00
表2:结束时间
id ts_end
123 02:00
123 07:00
输出:
id ts_start ts_end
123 01:00 02:00
123 03:00 NULL
123 05:00 07:00
123 09:00 NULL
我使用的是MySQL 5.7,因此尚无窗口/分析功能的访问权限,尽管如果这是最佳解决方案的一部分,那么我很乐意进行迁移(不过必须是开源的,因此是新版本的MySQL或Postgres)
谢谢
答案 0 :(得分:2)
您必须查看下一个起点是否晚于下一个终点。一种方法使用两个相关的子查询:
select id, ts_start,
(case when next_start > next_end then next_end
end) as ts_end
from (select s.*,
(select max(s2.ts_start)
from starts s2
where s2.id = s.id and s2.ts_start > s.ts_start
) as next_start,
(select min(e2.ts_end)
from ends e2
where e2.id = s.id and e2.ts_end > s.ts_end
) as next_end
from starts s
) s;
使用窗口函数,我将所有时间结合在一起,并查看下一个值:
with t as (
select id, ts_start as time, 'start' as which
from starts
union all
select id, ts_end, 'end'
from ends
)
select t.id, t.time as ts_start,
(case when next_which = 'end' then next_time
end) as ts_end
from (select t.*,
lead(time) over (partition by id order by time) as next_time,
lead(which) over (partition by id order by time) as next_which
from t
) t
where which = 'start';
答案 1 :(得分:2)
首先,您需要为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_end
和null
之间有任何开始时间(表start_time
)时,我们需要ts_start
作为ts_end
(第二行)。对于第二行,ts_end
必须为NULL
,因为开始时间5:00
在3:00
和7:00
之间。
对于第一个查询,我们可以使用带有HAVING
条件的NOT EXISTS
子句:
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 |
答案 2 :(得分:0)
在这种情况下,您可以尝试外部联接。例如:
Select st.id, st.ts_start,et.ts_end from startTime st left join endTime et on st.id=et.id;
通过这种方式,无论是否有结束时间,您都将获取开始时间的所有记录。
P.S:只需在查询中输入正确的表名即可。
答案 3 :(得分:0)
1)将当前和下一个ts_start
合并到一个查询中:
select
*,
(select min(ts_start) from table1 as tt1 where t1.id = tt1.id and t1.ts_start < tt1.ts_star) as next_start
from table1 as t1;
2)使用table2
加入此查询:
select *
from (
select
*,
(select min(ts_start) from table1 as tt1 where t1.id = tt1.id and t1.ts_start < tt1.ts_star) as next_start
from table1 as t1) as t1 left join
table2 as t2 on (t1.id = t2.id and t2.ts_end between t1.ts_start and t1.next_start);
应该可以在大多数基于SQL的DBMS上使用。
具有简化的数据类型和对象名称的演示:
with
t1(x,y) as (values(123,1),(123,3),(123,5),(123,9)),
t2(x,z) as (values(123,2),(123,7))
select *
from (
select
*,
(select min(y) from t1 as tt1 where t1.x = tt1.x and t1.y < tt1.y) as next
from t1) as t1 left join
t2 on (t1.x = t2.x and t2.z between t1.y and t1.next);