PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Zsumuj czas trwania nakładających się okresów z priorytetem, wykluczając samo nakładanie się

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ć.

Skrzypce SQL

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)


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak używać funkcji okna, aby określić, kiedy wykonać różne zadania w Hive lub Postgres?

  2. UPDATE z jsonb_set() wpływa tylko na jeden obiekt w zagnieżdżonej tablicy

  3. Używanie Postgresql z Grails :Brakująca sekwencja lub tabela:hibernate_sequence

  4. Jak znaleźć podciągi w SQL (Postgres)

  5. Różnica między tekstem a varchar (różne znaki)