2016-04-03 3 views
2

ich mit Online-Sitzungen wie folgt eine Tabelle (leere Zeilen sind nur für eine bessere Sichtbarkeit):Erhalten envelope.ie überlappende Zeitspannen

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 

Und ich brauche die „umhüllen“ Online-Zeit umspannt:

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 

ich habe diese Abfrage, die gewünschte Ergebnis liefert:

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; 

ich bin aber besorgt über die Leistung. Die Tabelle hat Hunderte von Millionen Zeilen und die Zeitauflösung ist Millisekunden (nicht eine Minute wie im Beispiel angegeben). So wird CTE t3 riesig sein. Hat jemand eine Lösung, die Self-Join und "CONNECT BY" vermeidet?

Ein einziges intelligenten Analytic Function wäre toll.

Antwort

3

Versuchen Sie diese auch. Ich kann es die besten getestet ich könnte, ich glaube, dass es alle Möglichkeiten abdeckt, einschließlich benachbarte Intervalle (10.15 bis 10.30 und 10.30 bis 10.40 Koaleszieren zu einem einzigen Intervall kombiniert, 10.15 bis 10.40 Uhr). Es sollte auch ziemlich schnell sein, es braucht nicht viel.

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

Schöne Lösung, ich sehe auch keine Probleme. –

+1

@WernfriedDomscheit - Wenn Sie sich immer noch für diese Art von Problem interessieren, fand ich, dass Stew Ashton eine bessere Lösung in seinem Blog hat. Es sollte ungefähr doppelt so schnell sein wie meine. https://stewashton.wordpress.com/2015/06/08/merging-overlapping-date-ranges/ – mathguy

+0

Great approach. Ja, es sollte schneller sein, da es keine "UNION ALL" enthält. Ich werde es testen. –

0

Ich denke, mit lag() und kumulativer Summe wird viele bessere Leistung haben:

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; 

gibt Ergebnis:

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

funktioniert nicht. IP 10.10.10.10 ist offline von 08:20:01 bis 08:59:59. IP 10.66.44.22 war online von 08.03 bis 08.11 Uhr (ich Ihre Antwort mit Ihrer Anfrage bearbeiten) –

1

Bitte diese Lösung testen, funktioniert es Ihre Beispiele für, aber es kann In einigen Fällen habe ich es nicht bemerkt. Kein Connect-by, kein Self-Join.

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 

Testdaten:

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

Ausgang:

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

schlägt fehl, wenn Sie auch eine Zeile wie diese ein: INSERT INTO 'IP_SESSIONS VALUES ('10 .10.10.10' , Zeitstempel- ' 2016.04.02 09.00.00' , Zeitstempel- ‚2016.04.02 09.16.00‘); ' –

+0

... denn wir haben zwei Sitzungen um 9:00 in diesem Fall starten. Ändern Sie "union all" in "union" (was die Leistung verringern kann) oder fügen Sie "rows unbounded previous and current row" in der dritten Zeile hinzu. –

+0

UNION statt UNION ALL wird nicht funktionieren, wenn Sie von 9.00 bis 09.12 Uhr zwei Intervalle haben und von 9.00 bis 09.15 Uhr, werden Sie das kürzere Intervall abholen und verpassen die von 9.12 bis 09.15 Uhr Intervall. Vorschlag: Versuchen Sie, nicht denselben Namen (io) für Tabellen und Spalten zu verwenden. Eine weitere Sache, diese Lösung kann Dinge wie 9:00 bis 9:12 und 9:12 bis 9:18 vermissen; vermutlich sollte das Ergebnis zwischen 9:00 und 9:18 liegen. Mögliche Lösung - in der Definition von sio, in der over-Klausel, ändern Sie die Reihenfolge in "Reihenfolge von t1, io desc". – mathguy

0

Am Ende habe ich mit einer Funktion endete, die meine Anforderungen erfüllt. Ich denke, es geht in eine ähnliche Richtung wie Ponder Stibbons antworten.

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;