Aktualizacja Moje oryginalne rozwiązanie nie było poprawne. Konsolidacja zakresów nie może być obsługiwana w zwykłym oknie. Pomyliłem się używając tej samej nazwy, trange
, zapominając, że okno znajduje się nad wierszami źródłowymi, a nie wierszami wynikowymi. Zobacz zaktualizowane SQL Fiddle
z pełnym zapytaniem, a także z dodanym rekordem ilustrującym problem.
Możesz uprościć nakładanie się wymagań, a także identyfikować luki i wyspy za pomocą typów zakresów PostgreSQL .
Poniższe zapytanie jest celowo pełne, aby pokazać każdy krok procesu. Wiele kroków można łączyć.
Najpierw dodaj inkluzywny [start, end]
zakres do każdego rekordu.
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)
Zidentyfikuj nakładające się zakresy określone przez &&
operatora i zaznacz początek nowych wysp za pomocą 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)
Ponumeruj grupy na podstawie sumy new_range
w obrębie 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
Grupuj według name, group_num
aby uzyskać całkowity czas spędzony na wyspie, a także pełny t_range
do wykorzystania w odliczaniu nakładania się.
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)
Aby uzyskać wymóg liczenia czasu nakładania się między A
wiadomości i B
wiadomości, znajdź wystąpienia, kiedy A
wiadomość nakłada się na B
wiadomość i użyj *
operator przecięcia, aby znaleźć przecięcie.
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)
Zsumuj łączny czas każdego nakładania się według 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)
Oblicz całkowity czas dla każdej 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)
Dołącz do łącznego czasu dla każdego name
do dostosowań z overlap_time
CTE i odejmij korektę dla końcowego duration
wartość.
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)