PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Somme des durées des périodes qui se chevauchent avec priorité en excluant le chevauchement lui-même

Mettre à jour Ma solution initiale n'était pas correcte. La consolidation des plages ne peut pas être gérée dans une fenêtre classique. Je me suis embrouillé en utilisant le même nom, trange , en oubliant que la fenêtre se trouve sur les lignes source plutôt que sur les lignes de résultat. Veuillez consulter le SQL Fiddle mis à jour avec la requête complète ainsi qu'un enregistrement ajouté pour illustrer le problème.

Vous pouvez simplifier l'exigence de chevauchement ainsi qu'identifier les lacunes et les îlots à l'aide de types de plage PostgreSQL .

La requête suivante est intentionnellement détaillée pour afficher chaque étape du processus. Plusieurs étapes peuvent être combinées.

SQL Fiddle

Tout d'abord, ajoutez un [start, end] inclusif plage à chaque enregistrement.

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)

Identifiez les plages qui se chevauchent comme déterminé par le && opérateur et marquer le début de nouvelles îles avec un 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)

Numérotez les groupes en fonction de la somme de new_range dans 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

Grouper par name, group_num pour obtenir le temps total passé sur l'île ainsi qu'une t_range complète à utiliser dans la déduction de chevauchement.

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)

Pour l'exigence de compter le temps de chevauchement entre A messages et B messages, trouver des occurrences de quand un A le message chevauche un B message, et utilisez le * opérateur d'intersection pour trouver l'intersection.

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)

Additionnez le temps total de chaque chevauchement par 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)

Calculer le temps total pour chaque 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)

Joignez le temps total pour chaque name aux ajustements du overlap_time CTE, et soustrayez l'ajustement pour la duration finale valeur.

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)