2016-04-03 1 views
2

が、私はこのようなオンラインセッションを持つテーブル(空の行は、見やすくするためにだけある)を持つまたがる:envelope.ie重複時間を取得します

ip_address | start_time  | stop_time 
------------|------------------|------------------ 
10.10.10.10 | 2016-04-02 08:00 | 2016-04-02 08:12 
10.10.10.10 | 2016-04-02 08:11 | 2016-04-02 08:20 

10.10.10.10 | 2016-04-02 09:00 | 2016-04-02 09:10 
10.10.10.10 | 2016-04-02 09:05 | 2016-04-02 09:08 
10.10.10.10 | 2016-04-02 09:05 | 2016-04-02 09:11 
10.10.10.10 | 2016-04-02 09:02 | 2016-04-02 09:15 
10.10.10.10 | 2016-04-02 09:10 | 2016-04-02 09:12 

10.66.44.22 | 2016-04-02 08:05 | 2016-04-02 08:07 
10.66.44.22 | 2016-04-02 08:03 | 2016-04-02 08:11 

をそして私は、オンラインの時間がまたがる「包み込む」必要があります。

WITH t AS 
    -- Determine full time-range of each IP 
    (SELECT ip_address, MIN(start_time) AS min_start_time, MAX(stop_time) AS max_stop_time FROM IP_SESSIONS GROUP BY ip_address), 
t2 AS 
    -- compose ticks 
    (SELECT DISTINCT ip_address, min_start_time + (LEVEL-1) * INTERVAL '1' MINUTE AS ts 
    FROM t 
    CONNECT BY min_start_time + (LEVEL-1) * INTERVAL '1' MINUTE <= max_stop_time), 
t3 AS 
    -- get all "online" ticks 
    (SELECT DISTINCT ip_address, ts 
    FROM t2 
     JOIN IP_SESSIONS USING (ip_address) 
    WHERE ts BETWEEN start_time AND stop_time), 
t4 AS 
    (SELECT ip_address, ts, 
     LAG(ts) OVER (PARTITION BY ip_address ORDER BY ts) AS previous_ts 
    FROM t3), 
t5 AS 
    (SELECT ip_address, ts, 
     SUM(DECODE(previous_ts,NULL,1,0 + (CASE WHEN previous_ts + INTERVAL '1' MINUTE <> ts THEN 1 ELSE 0 END))) 
      OVER (PARTITION BY ip_address ORDER BY ts ROWS UNBOUNDED PRECEDING) session_no 
    FROM t4) 
SELECT ip_address, MIN(ts) AS full_start_time, MAX(ts) AS full_stop_time 
FROM t5 
GROUP BY ip_address, session_no 
ORDER BY 1,2; 

は、しかし、私はパフォーマンスが心配です:

ip_address | full_start_time | full_stop_time 
------------|------------------|------------------ 
10.10.10.10 | 2016-04-02 08:00 | 2016-04-02 08:20 
10.10.10.10 | 2016-04-02 09:00 | 2016-04-02 09:15 
10.66.44.22 | 2016-04-02 08:03 | 2016-04-02 08:11 

私は望ましい結果を返すこのクエリを持っています。テーブルには何億もの行があり、時間の分解能はミリ秒です(例では1分ではありません)。従ってCTE t3は巨大になるでしょう。誰もが自己結合と "接続"を回避するソリューションを持っていますか?

単一のスマートAnalytic Functionは素晴らしいでしょう。

答えて

3

これも試してみてください。私はそれを可能な限りテストしました。隣接間隔の合体(10:15-10:30および10:30-10:40は1つの間隔、10:15-10:40にまとめられています)。それはまた非常に速くなければならない、それは多くを使用しない。

with m as 
     (
     select ip_address, start_time, 
        max(stop_time) over (partition by ip_address order by start_time 
          rows between unbounded preceding and 1 preceding) as m_time 
     from ip_sessions 
     union all 
     select ip_address, NULL, max(stop_time) from ip_sessions group by ip_address 
     ), 
    n as 
     (
     select ip_address, start_time, m_time 
     from m 
     where start_time > m_time or start_time is null or m_time is null 
     ), 
    f as 
     (
     select ip_address, start_time, 
      lead(m_time) over (partition by ip_address order by start_time) as stop_time 
     from n 
     ) 
select * from f where start_time is not null 
/
+0

良い解決策、私はどちらの問題も表示されません。 –

+1

@WernfriedDomscheit - このタイプの問題についてまだ気にしているなら、私はStew Ashtonが彼のブログでより良い解決策を見つけていることがわかりました。それは私の約2倍の速さでなければなりません。 https://stewashton.wordpress.com/2015/06/08/merging-overlapping-date-ranges/ – mathguy

+0

大きなアプローチ。はい、それは 'UNION ALL'を含んでいないので速くなければなりません。私はそれをテストします。 –

0

私はlag()を使用して、累積合計がはるかに優れた性能を持っているとしていると思う:

select ip_address, min(start_time) as full_start_time, 
     max(end_time) as full_end_time 
from (select t.*, 
      sum(case when prev_et >= start_time then 0 else 1 end) over 
       (partition by ip_address order by start_time) as grp 
     from (select s.*, 
        lag(end_time) over (partition by ip_address order by end_time) as prev_et 
      from ip_seesions s) 
      ) t 
group by grp, ip_address 
order by 1, 2; 

が結果を与える:

ip_address | full_start_time | full_stop_time 
------------|------------------|------------------ 
10.10.10.10 | 2016-04-02 08:00 | 2016-04-02 09:15 
10.10.10.10 | 2016-04-02 09:05 | 2016-04-02 09:12 
10.66.44.22 | 2016-04-02 08:03 | 2016-04-02 08:11 
10.66.44.22 | 2016-04-02 08:05 | 2016-04-02 08:07 
+0

動作しません。 IP 10.10.10.10は、08:20:01から08:59:59までオフラインでした。 IP 10.66.44.22が08:03から08:11までオンラインでした。(あなたの質問の結果であなたの答えを編集します) –

1

は、このソリューションをテストしてください、それはあなたの例のために動作しますが、そこかもしれません私が気付かなかったいくつかの場合があります。接続なし、自己結合なし。

with io as (
    select * from (
    select ip_address, t1, io, sum(io) over (partition by ip_address order by t1) sio 
     from (
     select ip_address, start_time t1, 1 io from ip_sessions 
     union all 
     select ip_address, stop_time, -1 io from ip_sessions)) 
    where (io = 1 and sio = 1) or (io = -1 and sio = 0)) 
select ip_address, t1, t2 
    from (
    select io.*, lead(t1) over (partition by ip_address order by t1) as t2 from io) 
    where io = 1 

テストデータ:

create table ip_sessions (ip_address varchar2(15), start_time date, stop_time date); 
insert into ip_sessions values ('10.10.10.10', timestamp '2016-04-02 08:00:00', timestamp '2016-04-02 08:12:00'); 
insert into ip_sessions values ('10.10.10.10', timestamp '2016-04-02 08:11:00', timestamp '2016-04-02 08:20:00'); 
insert into ip_sessions values ('10.10.10.10', timestamp '2016-04-02 09:00:00', timestamp '2016-04-02 09:10:00'); 
insert into ip_sessions values ('10.10.10.10', timestamp '2016-04-02 09:05:00', timestamp '2016-04-02 09:08:00'); 
insert into ip_sessions values ('10.10.10.10', timestamp '2016-04-02 09:02:00', timestamp '2016-04-02 09:15:00'); 
insert into ip_sessions values ('10.10.10.10', timestamp '2016-04-02 09:10:00', timestamp '2016-04-02 09:12:00'); 
insert into ip_sessions values ('10.66.44.22', timestamp '2016-04-02 08:05:00', timestamp '2016-04-02 08:07:00'); 
insert into ip_sessions values ('10.66.44.22', timestamp '2016-04-02 08:03:00', timestamp '2016-04-02 08:11:00'); 

出力:私は私の要件を満たす機能になってしまった終わり

IP_ADDRESS T1     T2 
----------- ------------------- ------------------- 
10.10.10.10 2016-04-02 08:00:00 2016-04-02 08:20:00 
10.10.10.10 2016-04-02 09:00:00 2016-04-02 09:15:00 
10.66.44.22 2016-04-02 08:03:00 2016-04-02 08:11:00 
+0

次のように行を挿入すると失敗します: 'INSERT INTO IP_SESSIONS VALUES('10 .10.10.10 '、TIMESTAMP' 2016-04-02 09:00:00 '、TIMESTAMP' 2016-04-02 09:16:00 ');' –

+0

...この場合、9:00から2つのセッションが開始されているためです。 'union all'を' union'(パフォーマンスを低下させる可能性があります)に変更するか、または3行目に 'unbounded previousとcurrent row'の間の行を追加してください。 –

+0

UNIONではなくUNIONが機能しません.9:00〜9:12と9:00〜9:15の2つの間隔がある場合、短い間隔を選択して9:12〜9:15を逃します間隔。提案:テーブルと列に同じ名前(io)を使用しないでください。もう1つのことは、この解決法は9時09分12秒と9時12分9時18分のようなものを見逃す可能性があります。おそらく結果は9:00〜9:18になるはずです。可能な修正 - sioの定義で、over節で、順序を "t1、io descによる順序"に変更します。 – mathguy

0

。 Ponder Stibbonsの答えに似た方向に向かうと思います。

CREATE OR REPLACE TYPE SESSION_REC AS OBJECT (START_TIME TIMESTAMP_UNCONSTRAINED, STOP_TIME TIMESTAMP_UNCONSTRAINED); 
CREATE OR REPLACE TYPE SESSION_TYPE AS TABLE OF SESSION_REC; 
CREATE OR REPLACE TYPE TIMESTAMP_TAB AS TABLE OF TIMESTAMP_UNCONSTRAINED; 

CREATE OR REPLACE FUNCTION ENVELOP_SESSIONS(v_ipaddress IN VARCHAR2) 
    RETURN SESSION_TYPE PIPELINED IS 

    rec SESSION_REC; 
    startTimes TIMESTAMP_TAB; 
    stopTimes TIMESTAMP_TAB; 

    TYPE ActionRecType IS RECORD (TS TIMESTAMP_UNCONSTRAINED, ACTION INTEGER); 
    TYPE ActionTableType IS TABLE OF ActionRecType; 
    actions ActionTableType; 
    onlineCount INTEGER := 0; 

BEGIN 

    SELECT START_TIME, STOP_TIME 
    BULK COLLECT INTO startTimes, stopTimes 
    FROM IP_SESSIONS 
    WHERE IP_ADDRESS = v_ipaddress; 

    WITH t AS 
     (SELECT COLUMN_VALUE AS ts, 1 AS action 
     FROM TABLE(startTimes) 
     UNION ALL 
     SELECT COLUMN_VALUE AS ts, -1 AS action 
     FROM TABLE(stopTimes)) 
    SELECT ts, action 
    BULK COLLECT INTO actions 
    FROM t 
    ORDER BY ts, action; 

    IF actions.COUNT > 0 THEN 
     FOR i IN actions.FIRST..actions.LAST LOOP  
      IF onlineCount = 0 AND actions(i).ACTION = 1 THEN 
       -- session starts 
       rec := SESSION_REC(actions(i).TS, NULL); 
      ELSIF onlineCount = 1 AND actions(i).ACTION = -1 THEN 
       -- session ends 
       rec := SESSION_REC(rec.START_TIME, actions(i).TS); 
       PIPE ROW(rec); 
      END IF; 
      onlineCount := onlineCount + actions(i).ACTION; 
     END LOOP;  
    END IF; 
    RETURN;  

END ENVELOP_SESSIONS; 
関連する問題