Myślę, że to załatwi sprawę:
WITH EVENTS AS (SELECT 'abc' usr, to_date('2016-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'login' event_type FROM dual UNION ALL
SELECT 'abc' usr, to_date('2016-01-01 08:25:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Stuff' event_type FROM dual UNION ALL
SELECT 'abc' usr, to_date('2016-01-01 10:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Stuff' event_type FROM dual UNION ALL
SELECT 'abc' usr, to_date('2016-01-01 14:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'login' event_type FROM dual UNION ALL
SELECT 'xyz' usr, to_date('2015-12-31 18:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'login' event_type FROM dual UNION ALL
SELECT 'xyz' usr, to_date('2016-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Logout' event_type FROM dual UNION ALL
SELECT 'def' usr, to_date('2016-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Logout' event_type FROM dual UNION ALL
SELECT 'def' usr, to_date('2016-01-01 08:15:00', 'yyyy-mm-dd hh24:mi:ss') event_ts, 'Logout' event_type FROM dual)
SELECT usr,
event_ts,
event_type,
SUM(counter) OVER (PARTITION BY usr ORDER BY event_ts) session_id
FROM (SELECT usr,
event_ts,
event_type,
CASE WHEN LAG(event_type, 1, 'Logout') OVER (PARTITION BY usr ORDER BY event_ts) = 'Logout' THEN 1
WHEN event_type = 'Logout' THEN 0
WHEN event_ts - LAG(event_ts) OVER (PARTITION BY usr ORDER BY event_ts) > 1/24 THEN 1
WHEN event_type = 'login' THEN 1
ELSE 0
END counter
FROM EVENTS);
USR EVENT_TS EVENT_TYPE SESSION_ID
--- ------------------- ---------- ----------
abc 2016-01-01 08:00:00 login 1
abc 2016-01-01 08:25:00 Stuff 1
abc 2016-01-01 10:00:00 Stuff 2
abc 2016-01-01 14:00:00 login 3
def 2016-01-01 08:00:00 Logout 1
def 2016-01-01 08:15:00 Logout 2
xyz 2015-12-31 18:00:00 login 1
xyz 2016-01-01 08:00:00 Logout 1
To rozwiązanie opiera się na obwodach logicznych, które mają miejsce w wyrażeniu CASE oraz na fakcie, że event_type nie jest null. Zakłada się również, że wielokrotne wylogowania z rzędu są liczone jako oddzielne sesje:
- Jeżeli poprzedni wiersz był wierszem wylogowania (i jeśli nie ma poprzedniego wiersza - tj. dla pierwszego wiersza w zestawie - potraktuj to tak, jakby był wiersz wylogowania), chcemy zwiększyć licznik o jeden. (Wylogowanie kończy sesję, więc zawsze mamy nową sesję po wylogowaniu.)
- Jeśli bieżący wiersz to wylogowanie, oznacza to zakończenie istniejącej sesji. Dlatego licznik nie powinien być zwiększany.
- Jeśli czas w bieżącym wierszu jest większy niż godzina od poprzedniego, zwiększ licznik o jeden.
- Jeśli bieżący wiersz jest wierszem logowania, to jest to nowa sesja, więc zwiększ licznik o jeden.
- W żadnym innym przypadku nie zwiększamy licznika.
Gdy już to zrobimy, wystarczy wykonać bieżące podsumowanie na ladzie.