PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Обща продължителност на припокриващи се периоди с приоритет чрез изключване на самото припокриване

Актуализация Първоначалното ми решение не беше правилно. Консолидирането на диапазони не може да се обработва в обикновен прозорец. Обърках се, като използвах същото име, trange , забравяйки, че прозорецът е над изходните редове, а не над редовете с резултати. Моля, вижте актуализирания SQL Fiddle с пълната заявка, както и добавен запис за илюстриране на проблема.

Можете да опростите изискването за припокриване, както и да идентифицирате пропуски и острови, като използвате типове диапазони на PostgreSQL .

Следната заявка е умишлено многословна, за да покаже всяка стъпка от процеса. Няколко стъпки могат да се комбинират.

SQL Fiddle

Първо добавете включващ [start, end] диапазон за всеки запис.

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)

Идентифицирайте припокриващи се диапазони, както е определено от && и маркирайте началото на нови острови с 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)

Номерирайте групите въз основа на сумата от new_range в 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

Групиране по name, group_num за да получите общото време, прекарано на острова, както и пълен t_range да се използва при приспадане на припокриване.

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)

За изискването за отчитане на времето на припокриване между A съобщения и B съобщения, намерете случаи на A съобщението се припокрива с B съобщение и използвайте * intersect оператор за намиране на пресечката.

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)

Сумирайте общото време на всяко припокриване по 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)

Изчислете общото време за всяко 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)

Присъединете се към общото време за всяко name към корекции от overlap_time CTE и извадете корекцията за крайната duration стойност.

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. Отменете текущата/активната заявка в Ruby on Rails

  2. Вземете N-тия елемент от масив, който се връща от функцията string_to_array().

  3. Създаване на PostgreSQL база данни

  4. Актуализирайте всеки ред с произволна дата и час между две дати

  5. Правилата за скоби на PostgreSQL, има ли обобщено ръководство?