Mysql 基于第一个表中的时差的SQL联接第二个表

Mysql 基于第一个表中的时差的SQL联接第二个表,mysql,sql,postgresql,join,Mysql,Sql,Postgresql,Join,我有两个表,一个表记录事件的开始时间,另一个表记录事件的结束时间,我想加入这两个表 然而,挑战在于并非每个开始事件都有对应的结束事件,如果是这样,我希望输出中有一个NULL。这可能吗 编辑:每个ID代表一个人,每天可以有多个事件开始和停止。对于每个事件,我只希望将单个“正确”的结束时间与开始时间(如果存在)合并。当前没有单独的事件级别标识符 例如: Select st.id, st.ts_start,et.ts_end from startTime st left join endTime et

我有两个表,一个表记录事件的开始时间,另一个表记录事件的结束时间,我想加入这两个表

然而,挑战在于并非每个开始事件都有对应的结束事件,如果是这样,我希望输出中有一个NULL。这可能吗

编辑:每个ID代表一个人,每天可以有多个事件开始和停止。对于每个事件,我只希望将单个“正确”的结束时间与开始时间(如果存在)合并。当前没有单独的事件级别标识符

例如:

Select st.id, st.ts_start,et.ts_end from startTime st left join endTime et on st.id=et.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)


谢谢

在这种情况下,您可以尝试外部联接。例如:

Select st.id, st.ts_start,et.ts_end from startTime st left join endTime et on st.id=et.id;
这样,无论结束时间是否可用,您都将获得开始时间的所有记录


注意:只需在查询中输入正确的表名。

在这种情况下,您可以尝试外部联接。例如:

Select st.id, st.ts_start,et.ts_end from startTime st left join endTime et on st.id=et.id;
这样,无论结束时间是否可用,您都将获得开始时间的所有记录


注意:只需在查询中输入正确的表名。

您必须查看下一个开始是否晚于下一个结束。一种方法使用两个相关子查询:

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';

你必须看看下一个起点是否比下一个终点晚。一种方法使用两个相关子查询:

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';
首先,您需要为
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\u start
ts\u end
之间有任何开始时间(表
start\u time
)时,我们需要
ts\u end
null
(第二行)。对于第二行
ts\u end
必须为
NULL
,因为有一个开始时间
5:00
,它介于
3:00
7:00
之间

对于第一个查询,我们可以使用带有
不存在
条件的
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
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 |
演示:

首先,您需要为
ts\u 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\u start
ts\u end
之间有任何开始时间(表
start\u time
)时,我们需要
ts\u end
null
(第二行)。对于第二行
ts\u end
必须为
NULL
,因为有一个开始时间
5:00
,它介于
3:00
7:00
之间

对于第一个查询,我们可以使用带有
不存在
条件的
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
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 |
演示:

1)将当前和下一个
ts\u 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;
应该能够在大多数基于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);
与
t1(x,y)为(值(123,1)、(123,3)、(123,5)、(123,9)),
t2(x,z)as(值(123,2)、(123,7))
挑选*
从(
挑选
*, 
(从t1中选择min(y)作为tt1,其中t1.x=tt1.x和t1.y
1)将当前和下一个
ts\u 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;
应该能够在大多数基于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);
与
t1(x,y)为(值(123,1)、(123,3)、(123,5)、(123,9)),
t2(x,z)as(值(123,2)、(123,7))
挑选*
从(
挑选
*, 
(从t1中选择min(y)作为tt1,其中t1.x=tt1.x和t1.y
我只想在每个开始时加入一个正确的结束时间(如果存在)。左连接/外部连接将为每个开始时间提供多个结束时间,这不是我想要的。因此,应该有一些标志来表示哪个开始时间属于哪个结束时间…您如何识别它?@Spcoggthesecond。这些查询中应该有
限制1
。相反,我只是添加了
min()
/
max()
,以获得相同的效果。我只想用一个正确的结束时间(如果存在)来连接每个开始。左连接/外部连接将为每个开始时间提供多个结束时间,这不是我想要的。因此,应该有一些标志来表示哪个开始时间属于哪个结束时间…您如何识别它?@Spcoggthesecond。这些查询中应该有
限制1
。相反,我只是添加了
min()。子查询返回多行?您好,我正在尝试您对相关子查询的回答,但是您的第一个子查询(选择s.*)在mysql中给了我一个错误:错误代码:1242。子查询返回的行数超过1行?