Aktualisieren Meine ursprüngliche Lösung war nicht richtig. Die Konsolidierung von Bereichen kann nicht in einem regulären Fenster durchgeführt werden. Ich habe mich selbst verwirrt, indem ich denselben Namen verwendet habe, trange
, wobei vergessen wird, dass sich das Fenster über den Quellzeilen und nicht über den Ergebniszeilen befindet. Bitte sehen Sie sich den aktualisierten SQL Fiddle
an mit der vollständigen Abfrage sowie einem hinzugefügten Datensatz zur Veranschaulichung des Problems.
Die folgende Abfrage ist absichtlich ausführlich, um jeden Schritt des Prozesses anzuzeigen. Mehrere Schritte können kombiniert werden.
Fügen Sie zuerst einen inklusiven [start, end]
hinzu Bereich zu jedem Datensatz.
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)
Identifizieren Sie überlappende Bereiche, wie durch &&
bestimmt Operator und markieren Sie den Beginn neuer Inseln mit einer 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)
Nummerieren Sie die Gruppen basierend auf der Summe von new_range
innerhalb von 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
Gruppieren nach name, group_num
um die Gesamtzeit, die auf der Insel verbracht wurde, sowie eine vollständige t_range
zu erhalten beim Überlappungsabzug zu verwenden.
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)
Für die Anforderung, die Überlappungszeit zwischen A
zu zählen Nachrichten und B
Nachrichten finden Vorkommen von A
Nachricht überlappt ein B
Nachricht, und verwenden Sie den *
Intersect-Operator, um den Schnittpunkt zu finden.
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)
Summieren Sie die Gesamtzeit jeder Überlappung nach 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)
Berechnen Sie die Gesamtzeit für jeden 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)
Treten Sie der Gesamtzeit für jeden name
bei zu Anpassungen von der overlap_time
CTE und subtrahieren Sie die Anpassung für die endgültige duration
Wert.
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)