sql - 通过排除重叠本身,将具有优先级的重叠时间段的持续时间相加

标签 sql postgresql

我有一个 R 代码,我正尝试在 PostgreSQL 中重写它以提供 grafana 仪表板。我确实有基础知识,所以我几乎完成了脚本的其他部分,但我现在想在 PostgreSQL 中完成的事情超出了我的能力范围。我在 StackOverflow 上看到非常相似的已解决问题,但我似乎无法让它们为我工作。以下是一些我尝试改编的代码链接

https://stackoverflow.com/a/54370027/7885817
https://stackoverflow.com/a/44139381/7885817
对于我发布的重复性问题,我表示赞同。
非常感谢任何帮助!

所以,我的问题是:
我有时间戳重叠的消息。这些消息具有优先级:AB(A更重要),开始时间结束我

严格来说:我想将 A 和 B 的持续时间相加 但是如果有重叠,我想找到优先级为 A 的消息的第一个开始时间和最后一个结束时间之间的持续时间,对于优先级为 B 的消息也是如此。如果 A 消息与 B 消息重叠,我想拆分这个持续时间在 A 消息的结束时间,直到那一点 B 消息的持续时间被分配给 A。 我做了一个视觉来支持我的神秘解释和我的数据的简化版本:

CREATE TABLE activities(
    id int,
    name text,
    start timestamp,
    "end" timestamp
);

INSERT INTO activitiesVALUES
(1, 'A', '2018-01-09 17:00:00', '2018-01-09 20:00:00'),
(2, 'A', '2018-01-09 18:00:00', '2018-01-09 20:30:00'),
(3, 'B', '2018-01-09 19:00:00', '2018-01-09 21:30:00'),
(4, 'B', '2018-01-09 22:00:00', '2018-01-09 23:00:00');

SELECT * FROM activities;

enter image description here

非常感谢您的宝贵时间!

最佳答案

更新 我原来的解决方案是不正确的。范围合并无法在常规窗口中处理。我使用相同的名称混淆了自己,trange ,忘记了窗口位于源行上方而不是结果行上方。请参阅更新的 SQL Fiddle使用完整的查询以及添加的记录来说明问题。

您可以使用 PostgreSQL range types 简化重叠要求并识别间隙和孤岛。 .

以下查询故意冗长以显示流程的每个步骤。可以组合多个步骤。

SQL Fiddle

首先,添加一个包容性的[start, end]每条记录的范围。

with add_ranges as (
  select id, name, tsrange(start, "end", '[]') as t_range
    from activities
), 

 id | name |                    t_range                    
----+------+-----------------------------------------------
  1 | A    | ["2018-01-09 17:00:00","2018-01-09 20:00:00"]
  2 | A    | ["2018-01-09 18:00:00","2018-01-09 20:30:00"]
  3 | B    | ["2018-01-09 19:00:00","2018-01-09 21:30:00"]
  4 | B    | ["2018-01-09 22:00:00","2018-01-09 23:00:00"]
(4 rows)

识别由 && 确定的重叠范围运算符并用 1 标记新岛屿的开始.

mark_islands as (
  select id, name, t_range,
         case
           when t_range && lag(t_range) over w then 0
           else 1
         end as new_range
    from add_ranges
  window w as (partition by name order by t_range)
),

 id | name |                    t_range                    | new_range 
----+------+-----------------------------------------------+-----------
  1 | A    | ["2018-01-09 17:00:00","2018-01-09 20:00:00"] |         1
  2 | A    | ["2018-01-09 18:00:00","2018-01-09 20:30:00"] |         0
  3 | B    | ["2018-01-09 19:00:00","2018-01-09 21:30:00"] |         1
  4 | B    | ["2018-01-09 22:00:00","2018-01-09 23:00:00"] |         1
(4 rows)

根据 new_range 的总和对组进行编号在 name 内.

group_nums as (
  select id, name, t_range, 
         sum(new_range) over (partition by name order by t_range) as group_num
    from mark_islands
),

 id | name |                    t_range                    | group_num 
----+------+-----------------------------------------------+-----------
  1 | A    | ["2018-01-09 17:00:00","2018-01-09 20:00:00"] |         1
  2 | A    | ["2018-01-09 18:00:00","2018-01-09 20:30:00"] |         1
  3 | B    | ["2018-01-09 19:00:00","2018-01-09 21:30:00"] |         1
  4 | B    | ["2018-01-09 22:00:00","2018-01-09 23:00:00"] |         2

分组依据 name, group_num获得在岛上花费的总时间以及完整的t_range用于重叠扣除。

islands as (
  select name,
         tsrange(min(lower(t_range)), max(upper(t_range)), '[]') as t_range,
         max(upper(t_range)) - min(lower(t_range)) as island_time_interval
    from group_nums
   group by name, group_num
),

 name |                    t_range                    | island_time_interval 
------+-----------------------------------------------+----------------------
 A    | ["2018-01-09 17:00:00","2018-01-09 20:30:00"] | 03:30:00
 B    | ["2018-01-09 19:00:00","2018-01-09 21:30:00"] | 02:30:00
 B    | ["2018-01-09 22:00:00","2018-01-09 23:00:00"] | 01:00:00
(3 rows)

对于统计A之间重叠时间的需求消息和B消息,查找出现 A 的时间消息与 B 重叠消息,并使用 *求交点运算符。

priority_overlaps as (
  select b.name, a.t_range * b.t_range as overlap_range
    from islands a
    join islands b
      on a.t_range && b.t_range
     and a.name = 'A' and b.name != 'A'
),

 name |                 overlap_range                 
------+-----------------------------------------------
 B    | ["2018-01-09 19:00:00","2018-01-09 20:30:00"]
(1 row)

name将每次重叠的总时间相加.

overlap_time as (
  select name, sum(upper(overlap_range) - lower(overlap_range)) as total_overlap_interval
    from priority_overlaps
   group by name
),

 name | total_overlap_interval 
------+------------------------
 B    | 01:30:00
(1 row)

计算每个name的总时间.

island_times as (
  select name, sum(island_time_interval) as name_time_interval
    from islands
   group by name
)

 name | name_time_interval 
------+--------------------
 B    | 03:30:00
 A    | 03:30:00
(2 rows)

加入每个name的总时间从 overlap_time 调整CTE,并减去最终的调整 duration值(value)。

select i.name,
       i.name_time_interval - coalesce(o.total_overlap_interval, interval '0') as duration
  from island_times i
  left join overlap_time o
    on o.name = i.name
;

 name | duration 
------+----------
 B    | 02:00:00
 A    | 03:30:00
(2 rows)

关于sql - 通过排除重叠本身,将具有优先级的重叠时间段的持续时间相加,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62953977/

相关文章:

python - Mac 上安装 Odoo 无法执行命令 LESSC

arrays - 如何循环遍历 JSONb 字段中包含的数组?

javascript - 未处理的 promise 拒绝警告 : error: password authentication failed for user

ruby-on-rails - 尝试 db :migrate inside of heroku, 迁移失败

sql - 错误 PLS-00103 : Encountered the symbol "DECLARE"?

php - 数据库表更新不起作用?

sql - 从表中插入/查询数据时出现 ORA-00904

sql - PostgreSQL GROUP BY LOWER() 不工作

java - 从文件中读取数据并将其添加到数据库中,但不添加相同的值

SQL SERVER 检查约束 - 查询其他行