2016-04-03 23 views
2

Böyle çevrimiçi oturumların içeren bir tablo (boş satırlar daha iyi görüş için sadece beklenir) olması yayılan:envelope.ie örtüşen zaman alın

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 

Ve çevrimiçi zamanı yayılan "saracak" gerekir:

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; 

Ancak, performans hakkında endişe duyuyorum:

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 

Ben istenen sonuç döndürür bu sorgu var. Tabloda yüz milyonlarca satır vardır ve zaman çözünürlüğü milisaniyedir (örnekte verilen bir Dakika değil). Böylece CTE t3 büyük olacak. Herkesin kendi kendine birleşmesini ve "BAĞLANTIYI" nı engelleyen bir çözümü var mı? Tek bir akıllı Analytic Function harika olurdu.

cevap

3

Bunu da deneyin. Elimden gelenin en iyisini yaptım, buna ek olarak, bitişik aralıkların birleştirilmesi de dahil olmak üzere tüm olasılıkları kapsadığına inanıyorum (10:15 - 10:30 ve 10:30 - 10:40 arası 10:15 - 10:40). Ayrıca oldukça hızlı olmalı, fazla kullanmıyor.

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

Güzel çözüm, ben de herhangi bir sorun görmüyorum. –

+1

@WernfriedDomscheit - Bu tür bir sorunu hala önemsiyorsanız, Stew Ashton'ın blogunda daha iyi bir çözüm bulduğunu gördüm. Benimki kadar iki kat daha hızlı olmalı. https://stewashton.wordpress.com/2015/06/08/merging-overlapping-date-ranges/ – mathguy

+0

Harika bir yaklaşım. Evet, 'UNION ALL' içermediği için daha hızlı olmalı. Ben test edeceğim. –

0

ben lag() kullanarak düşünüyorum ve kümülatif toplamı çok daha iyi performansa sahip olacak:

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; 

sonuç verir:

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

Çalışmıyor. IP 10.10.10.10, 08:20:01 ile 08:59:59 saatleri arasında çevrimdışıydı. IP 10.66.44.22 çevrim içi 08:03 08:11 idi (cevabınızı bu sorgu sonucuyla düzenledim) –

1

bu çözümü test edin, sizin örnekler için çalışır, ancak orada may fark etmediğim bazı durumlar. Bağlanma yok, kendi kendine katılma yok.

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 

Testi veriler:

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'); 

Çıktı: Ben gerekliliklerini yerine getiren bir işlevi ile sona erdi Sonunda

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

Ayrıca şu şekilde bir satır eklerseniz başarısız olur: "IP_SESSIONS DEUES INSERT INSERIUE ('10 .10.10.10 ', TIMESTAMP' 2016-04-02 09:00:00 ', TIMESTAMP' 2016-04-02 09:16:00 '); –

+0

... çünkü bu durumda saat 9: 00'dan itibaren iki seansımız var. Sendika'yı "sendika" olarak değiştirin (performansı düşürebilir) veya üçüncü satırda "sınırsız önceki ve geçerli satırlar arasındaki satırları" ekleyin. UNION ALL yerine –

+0

UNION çalışmayacak, 9:00 ila 9:12 ve 9: 00'dan 9: 15'e kadar iki aralığınız varsa, daha kısa aralığı alacak ve 9:12 - 9:15 Aralık. Öneri: tablolar ve sütunlar için aynı ismi (io) kullanmamaya çalışın. Bir şey daha, bu çözüm 9:00 ila 9:12 ve 9:12 ila 9:18 gibi şeyleri kaçırabilir; Muhtemelen sonuç 9:00 ile 09:18 arasında olmalıdır. Muhtemel düzeltme - sio'nun tanımında, yukarıdaki maddede, siparişi "t1, io desc" ile sipariş etmek için değiştirin. – mathguy

0

. Sanırım, Ponder Stibbons'ın yanıtı gibi benzer bir yöne gidiyor.

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; 
İlgili konular