2012-04-08 7 views
5

Ho una tabella con una serie di valori (IP varchar (15), DateTime datetime2). Ogni riga corrisponde a una richiesta HTTP effettuata da un utente. Voglio assegnare i numeri di sessione a queste righe. Indirizzi IP diversi hanno numeri di sessione diversi. Lo stesso IP deve essere assegnato a un nuovo numero di sessione se l'ultima richiesta è precedente a 30 minuti. Ecco un esempio di output:SQL Server: row_number partizionato dal timeout

IP,  DateTime,   SessionNumber, RequestNumber 
1.1.1.1, 2012-01-01 00:01, 1,    1 
1.1.1.1, 2012-01-01 00:02, 1,    2 
1.1.1.1, 2012-01-01 00:03, 1,    3 
1.1.1.2, 2012-01-01 00:04, 2,    1 --different IP => new session number 
1.1.1.2, 2012-01-01 00:05, 2,    2 
1.1.1.2, 2012-01-01 00:40, 3,    1 --same IP, but last request 35min ago (> 30min) 

colonne 1 e 2 sono ingressi, 3 e 4 sono le uscite desiderate. La tabella mostra due utenti.

Poiché la tabella sottostante è veramente grande, come può essere risolto in modo efficiente? Preferirei una piccola quantità costante di passaggi sui dati (uno o due).

+0

Quale versione di SQL Server? Se 2012 la nuova clausola 'OVER' funzionerà. –

+0

Sì, è SQL Server 2012. – usr

risposta

8

Ecco un paio di tentativi.

;WITH CTE1 AS 
(
SELECT *, 
IIF(DATEDIFF(MINUTE, 
     LAG(DateTime) OVER (PARTITION BY IP ORDER BY DateTime), 
     DateTime) < 30,0,1) AS SessionFlag 
FROM Sessions 
), CTE2 AS 
(
SELECT *, 
     SUM(SessionFlag) OVER (PARTITION BY IP 
            ORDER BY DateTime) AS IPSessionNumber 
FROM CTE1 
) 
SELECT IP, 
     DateTime, 
     DENSE_RANK() OVER (ORDER BY IP, IPSessionNumber) AS SessionNumber, 
     ROW_NUMBER() OVER (PARTITION BY IP, IPSessionNumber 
           ORDER BY DateTime) AS RequestNumber 
FROM CTE2 

Questo ha due operazioni di ordinamento (per IP, DateTime poi da IP, IPSessionNumber), ma si assume che il SessionNumber possono essere assegnati arbitrariamente finché un diverso numero di sessione univoco viene assegnato a ogni nuova sessione per l'indirizzo IP/30 minuti regola.

Per assegnare i valori SessionNumber in modo sequenziale in ordine cronologico. Ho usato il seguente.

;WITH CTE1 AS 
(
SELECT *, 
IIF(DATEDIFF(MINUTE, 
     LAG(DateTime) OVER (PARTITION BY IP ORDER BY DateTime), 
     DateTime) < 30,0,1) AS SessionFlag 
FROM Sessions 
), CTE2 AS(
SELECT *, 
     SUM(SessionFlag) OVER (ORDER BY DateTime) AS GlobalSessionNo 
FROM CTE1 
), CTE3 AS(
SELECT *, 
     MAX(CASE WHEN SessionFlag = 1 THEN GlobalSessionNo END) 
       OVER (PARTITION BY IP ORDER BY DateTime) AS SessionNumber 
FROM CTE2) 
SELECT IP, 
     DateTime, 
     SessionNumber, 
     ROW_NUMBER() OVER (PARTITION BY SessionNumber 
           ORDER BY DateTime) AS RequestNumber 
FROM CTE3 

Tuttavia, il numero di operazioni di ordinamento aumenta a 4.

+0

Se le richieste provenienti da due interleave IP, le loro sessioni non si confonderanno? – Andomar

+0

@Andomar - Buon punto! Fisso. –

+0

L'utilizzo di un conteggio finestre è geniale! Ricorderò quel trucco. – usr

2

Ecco una versione che utilizza una variabile di tabella e row_number per creare un ID che può essere utilizzato in un CTE ricorsivo. Potrebbe valere la pena confrontare le prestazioni con il cursore e una query (fornite da Martin).

CREATE TABLE #T 
(
    IP varchar(15), 
    DateTime datetime, 
    ID int, 
    primary key (IP, ID) 
) 

insert into #T(IP, DateTime, ID) 
select IP, DateTime, row_number() over(partition by IP order by DateTime) 
from #sessionRequests 

;with C as 
(
    select IP, 
     ID, 
     DateTime, 
     1 as Session 
    from #T 
    where ID = 1 
    union all 
    select T.IP, 
     T.ID, 
     T.DateTime, 
     C.Session + case when datediff(minute, C.DateTime, T.DateTime) >= 30 then 1 else 0 end 
    from #T as T 
    inner join C 
     on T.IP = C.IP and 
     T.ID = C.ID + 1 
) 
SELECT IP, 
     DateTime, 
     dense_rank() over(order by IP, Session) as SessionNumber, 
     row_number() over(partition by IP, Session order by DateTime) as RequestNumber 
from C 
order by IP, DateTime, SessionNumber, RequestNumber 
option (maxrecursion 0) 
+1

Mi piace questa versione perché è facile da estendere, quasi come l'approccio basato sul cursore. L'ho modificato per utilizzare una tabella temporanea che risolveva un problema di ottimizzazione (le variabili di tabella non hanno statistiche). Inoltre, ho verificato che questo codice funziona. Grazie! – usr