mysql - SQL根据第一个表中的时间差连接第二个表

标签 mysql sql postgresql join

我有两个表,一个是事件的开始时间,第二个是事件的结束时间,我想加入两个

然而,挑战在于每个开始事件并不总是有相应的结束事件,如果是这样,我希望在输出中为 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)

谢谢

最佳答案

首先你需要得到一个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 之间有任何开始时间(表 start_time)时,我们需要 ts_endnull(第二行) ts_end。对于第二行 ts_end 必须是 NULL,因为有一个开始时间 5:00 介于 3:007: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

关于mysql - SQL根据第一个表中的时间差连接第二个表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51798794/

相关文章:

postgresql - 如何将现有的 postgres 数据文件夹复制和使用到 docker postgres 容器中

sql - Postgresql PSQL 序列凝视。为什么这不执行完成?

MySQL Workbench 数据库错误

mysql - 从按分钟的原始数据聚合 mysql 数据

sql - MySQL:选择出现的一行,然后将结果与另一个查询结合起来

postgresql - 如何配置 postgresql 使其接受登录名+密码身份验证?

mysql - 在不同数据库上组合两个子选择而无需引用

mysql - 求和mysql中的一列

sql - MySQL 中的减号?

sql - Redmine:多次复制问题