Cập nhật Giải pháp ban đầu của tôi không đúng. Việc hợp nhất các phạm vi không thể được xử lý trong một cửa sổ thông thường. Tôi đã tự nhầm lẫn khi sử dụng cùng một tên, trange
, quên rằng cửa sổ nằm trên các hàng nguồn chứ không phải các hàng kết quả. Vui lòng xem SQL Fiddle
được cập nhật với truy vấn đầy đủ cũng như một bản ghi được thêm vào để minh họa sự cố.
Bạn có thể đơn giản hóa yêu cầu chồng chéo cũng như xác định các khoảng trống và đảo bằng cách sử dụng các loại phạm vi PostgreSQL .
Truy vấn sau đây cố ý dài dòng để hiển thị từng bước của quy trình. Một số bước có thể được kết hợp.
Đầu tiên, thêm [start, end]
bao gồm phạm vi cho mỗi bản ghi.
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)
Xác định các phạm vi chồng chéo như được xác định bởi &&
toán tử và đánh dấu sự bắt đầu của các đảo mới bằng 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)
Đánh số các nhóm dựa trên tổng của new_range
trong 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
Nhóm theo name, group_num
để có được tổng thời gian trên đảo cũng như t_range
hoàn chỉnh được sử dụng trong khấu trừ chồng chéo.
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)
Đối với yêu cầu đếm thời gian chồng chéo giữa A
tin nhắn và B
thông báo, tìm các lần xuất hiện khi một A
thông báo chồng lên một B
và sử dụng *
toán tử giao nhau để tìm giao lộ.
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)
Tính tổng thời gian của mỗi lần chồng chéo theo 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)
Tính tổng thời gian cho mỗi 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)
Tham gia tổng thời gian cho mỗi name
đối với các điều chỉnh từ overlap_time
CTE và trừ đi điều chỉnh cho duration
cuối cùng giá trị.
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)