sql >> データベース >  >> RDS >> PostgreSQL

重複自体を除外することにより、優先的に重複期間の期間を合計する

    更新 私の元の解決策は正しくありませんでした。範囲の統合は、通常のウィンドウでは処理できません。同じ名前trangeを使って混乱しました 、ウィンドウが結果行ではなくソース行の上にあることを忘れています。更新された SQLFiddle をご覧ください。 完全なクエリと、問題を説明するための追加のレコードが含まれています。

    PostgreSQLの範囲タイプを使用して、重複する要件を簡素化し、ギャップやアイランドを特定できます。

    次のクエリは、プロセスの各ステップを示すために意図的に冗長になっています。いくつかのステップを組み合わせることができます。

    SQLフィドル

    まず、包括的な[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と重複しています メッセージを送信し、*を使用します 交差演算子を使用して交差を見つけます。

    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. MySQLクエリキャッシュを有効にする方法

    2. スコープでフィールドを拡張するレール、PGはそれを好きではありません

    3. オラクルのsys_refcursorをc#に返します

    4. INTを使用して、文字列を使用するよりもはるかに遅い数値を含むVarcharインデックスを選択するのはなぜですか?