sql >> Databasteknik >  >> RDS >> PostgreSQL

Summa varaktigheten av överlappande perioder med prioritet genom att exkludera själva överlappningen

Uppdatera Min ursprungliga lösning var inte korrekt. Konsolideringen av intervall kan inte hanteras i ett vanligt fönster. Jag förvirrade mig själv genom att använda samma namn, trange , glömmer att fönstret är över källraderna snarare än resultatraderna. Se den uppdaterade SQL-fiolen med hela frågan samt en tillagd post för att illustrera problemet.

Du kan förenkla kravet på överlappning samt identifiera luckor och öar med PostgreSQL-intervalltyper .

Följande fråga är avsiktligt utförlig för att visa varje steg i processen. Ett antal steg kan kombineras.

SQL Fiddle

Lägg först till en inklusive [start, end] intervall till varje post.

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)

Identifiera överlappande intervall som bestäms av && operatör och markera början på nya öar med en 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)

Numrera grupperna baserat på summan av new_range inom 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

Gruppera efter name, group_num för att få den totala tiden som spenderats på ön samt ett komplett t_range som ska användas vid överlappningsavdrag.

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 kravet att räkna överlappningstid mellan A meddelanden och B meddelanden, hitta förekomster av när en A meddelande överlappar ett B meddelande och använd * skärningsoperator för att hitta korsningen.

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)

Summa den totala tiden för varje överlappning med 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)

Beräkna den totala tiden för varje 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)

Gå med i den totala tiden för varje name till justeringar från overlap_time CTE och subtrahera justeringen för den sista duration värde.

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. Säkring 6.3 dbcp grundläggande datakälla

  2. Endast en nyckel från sammansatt primärnyckel som främmande nyckel

  3. Spool-kommando:Mata inte ut SQL-satsen till filen

  4. Sammankopplingsproblem