PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Summendauer sich überschneidender Perioden mit Priorität durch Ausschließen der Überschneidung selbst

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.

Mit PostgreSQL-Bereichstypen können Sie die Überschneidungsanforderung vereinfachen und Lücken und Inseln identifizieren .

Die folgende Abfrage ist absichtlich ausführlich, um jeden Schritt des Prozesses anzuzeigen. Mehrere Schritte können kombiniert werden.

SQL-Geige

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)