sql >> Databáze >  >> RDS >> PostgreSQL

Sečíst trvání překrývajících se období s prioritou vyloučením samotného překrytí

Aktualizovat Moje původní řešení nebylo správné. Konsolidaci rozsahů nelze zvládnout v běžném okně. Zmátl jsem se tím, že jsem použil stejný název, trange , zapomínáme, že okno je nad zdrojovými řádky, nikoli nad výslednými řádky. Přečtěte si aktualizovaný SQL Fiddle s úplným dotazem i s přidaným záznamem pro ilustraci problému.

Požadavek na překrývání a identifikaci mezer a ostrovů můžete zjednodušit pomocí typů rozsahů PostgreSQL .

Následující dotaz je záměrně podrobný, aby ukázal každý krok procesu. Je možné kombinovat několik kroků.

SQL Fiddle

Nejprve přidejte včetně [start, end] rozsah ke každému záznamu.

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)

Identifikujte překrývající se rozsahy podle && operátor a označte začátek nových ostrovů 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)

Očíslujte skupiny podle součtu new_range v rámci 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

Seskupit podle name, group_num získat celkový čas strávený na ostrově a také kompletní t_range k použití při odpočtu překrytí.

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)

Pro požadavek počítat dobu překrytí mezi A zprávy a B zprávy, najít výskyty, kdy A zpráva překrývá B a použijte * operátor intersect k nalezení křižovatky.

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)

Sečtěte celkový čas každého překrytí podle 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)

Vypočítejte celkový čas pro každý 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)

Připojte celkový čas pro každé name na úpravy z overlap_time CTE a odečtěte úpravu pro konečné duration hodnotu.

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. SEC_CASE_SENSTIVE_LOGON za 12c

  2. MySQL vrací přesnou shodu slov z textového obsahu

  3. Jak získat živé aktualizace oznámení z mysql pomocí webových soketů?

  4. Převezměte kontrolu nad svými daty pomocí Microsoft Access