2016-06-27 3 views
1

ビデオ内で発生する特定のイベントのデータを収集しました。そのビデオ内で何らかのイベントが発生した合計時間を把握する必要がありますが、複数のイベントが同時に発生している期間を2倍にすることはできません。このイメージは状況を示しています。このシナリオではビデオ内のイベントのオーバーラップしていない時間を返すクエリ

enter image description here

、全体の10秒のビデオの7秒を取る4つのイベントがあります。単純に各イベントの合計時間を合計すると、3 + 2 + 3 + 2 = 10 out of 10 secondsが正しく生成されません。私が働いているテーブルにはあります。

video_id, video_length, event_id, event_start, event_end 

誰もが、私はこれがギャップと島問題と呼ばれている

+1

ご使用のデータベースに質問にタグを付けてください。 –

答えて

1

を探していた結果をもたらすために、クエリを書くことができる方法を知っています。基本的には、重複するレコードのグループを見つける必要があります。何かが始まると、最初のレコードを特定することでこれを行うことができます。グループはそのようなフラグの合計です。

次の例では、2つのイベントが同時に開始されないと仮定して、開始時刻と終了時刻を「島」としています。

select video_id, min(event_start) as event_start, max(event_end) as event_end 
from (select e.*, 
      sum(IsNotOverlap) over (partition by video_id order by event_start) as grp 
     from (select e.*, 
        (case when exists (select 1 from events e2 where e2.event_start < e.event_start and e2.event_end > e.event_start and e2.video_id = v.video_id) 
         then 0 else 1 
        end) as IsNotOverlap 
      from events e 
      ) e 
    ) e 
group by video_id, grp; 

これをサブクエリまたはCTEとして使用して、特定のビデオの合計時間を取得できます。

0

これは、2つのイベントが一つのイベントが完全に別の中に含まれている場合でも、同じ開始日、終了日または持っている場合でも動作します:

Oracleのセットアップ

CREATE TABLE videos (video_id, video_length, event_id, event_start, event_end) AS 
SELECT 1, 10, 1, 1, 4 FROM DUAL UNION ALL 
SELECT 1, 10, 2, 1, 3 FROM DUAL UNION ALL -- Same start date 
SELECT 1, 10, 3, 2, 4 FROM DUAL UNION ALL -- Same end date 
SELECT 1, 10, 4, 3, 6 FROM DUAL UNION ALL 
SELECT 1, 10, 5, 7, 9 FROM DUAL UNION ALL 
SELECT 1, 10, 6, 8, 8.5 FROM DUAL;  -- Contained in previous event 

クエリ

SELECT video_id, 
     SUM(event_duration) AS event_duration, 
     MAX(video_length) AS video_length 
FROM (
    SELECT video_id, 
     video_length, 
     end_date 
      - LAST_VALUE(start_date) IGNORE NULLS 
       OVER (PARTITION BY video_id 
         ORDER BY ROWNUM) AS event_duration 
    FROM (
    SELECT video_id, 
      video_length, 
      CASE WHEN 1 = lvl 
       AND 1 = SUM(lvl) OVER (PARTITION BY video_id 
              ORDER BY event_date, lvl DESC, ROWNUM) 
       THEN event_date 
       END AS start_date, 
      CASE WHEN 0 = SUM(lvl) OVER (PARTITION BY video_id 
              ORDER BY event_date, lvl DESC, ROWNUM) 
       THEN event_date 
       END AS end_date 
    FROM videos 
    UNPIVOT (event_date FOR lvl IN (event_start AS 1, event_end AS -1)) 
) 
) 
GROUP BY video_id; 

出力:複雑

VIDEO_ID EVENT_DURATION VIDEO_LENGTH 
---------- -------------- ------------ 
     1    7   10 
0

バリアント1: (VIDEO_IDによって常にパーティション、start_dateの順。)まずEND_DATEからMAXを実行します、その後、前のレコードから最大で始まるイベントを比較します。開始時に< = max end_dateを実行中に重複があります。次に、ランニングサムを使用して重複間隔のグループを作成し、最後にこれらのグループをグループ化しています。

SELECT video_id, video_length, SUM (new_end - new_start) total_time 
    FROM ( SELECT video_id, video_length, MIN (event_start) new_start, MAX (new_end) new_end 
       FROM (SELECT b.*, SUM (counting) OVER (PARTITION BY video_id ORDER BY event_start) time_group 
         FROM (SELECT a.*, CASE WHEN LAG (new_end, 1) OVER (PARTITION BY video_id ORDER BY event_start) >= event_start THEN NULL ELSE 1 END counting 
           FROM (SELECT x.*, MAX (event_end) OVER (PARTITION BY video_id ORDER BY event_start) new_end 
             FROM videos x) a) b) c 
      GROUP BY video_id, video_length, time_group) 
GROUP BY video_id, video_length 
ORDER BY video_id 

バリアント2:期間(または同じ期間)をオーバーラップの開始と終了を取得し、重複しない値だけを取得し、時間を合計:

SELECT video_id, SUM (new_end - new_start) total_time 
    FROM (SELECT DISTINCT a.video_id, 
          (SELECT MIN (event_start) 
          FROM videos b 
          WHERE ((a.event_start BETWEEN b.event_start AND b.event_end) OR (a.event_end BETWEEN b.event_start AND b.event_end)) AND a.video_id = b.video_id) 
          new_start, 
          (SELECT MAX (event_end) 
          FROM videos b 
          WHERE ((a.event_start BETWEEN b.event_start AND b.event_end) OR (a.event_end BETWEEN b.event_start AND b.event_end)) AND a.video_id = b.video_id) 
          new_end 
      FROM videos a) 
GROUP BY video_id 

変形例3:これは、変形例2です、しかし、12 LATERAL Inline Views

SELECT video_id, SUM (new_end - new_start) total_time 
    FROM (SELECT DISTINCT a.video_id, b.new_start, b.new_end 
      FROM videos a, 
       LATERAL (SELECT MIN (event_start) new_start, MAX (event_end) new_end 
          FROM videos b 
          WHERE ((a.event_start BETWEEN b.event_start AND b.event_end) OR (a.event_end BETWEEN b.event_start AND b.event_end)) AND a.video_id = b.video_id) b) 
GROUP BY video_id 

あなたが使用できるOracleの新機能を使用するように変更subquがあるため、同じ結果を与えるCROSS APPLY JoinすぎたりOUTER APPLY Join、 eryは常に1つの行を返します。

関連する問題