2012-04-08 9 views
5

Ich habe eine Tabelle mit einer Reihe von (IP varchar (15), Datetime datetime2) Werte. Jede Zeile entspricht einer HTTP-Anfrage eines Benutzers. Ich möchte Sitzungsnummern diesen Zeilen zuweisen. Unterschiedliche IP-Adressen haben unterschiedliche Sitzungsnummern. Der gleichen IP-Adresse sollte eine neue Sitzungsnummer zugewiesen werden, wenn die letzte Anfrage älter als 30 Minuten ist. Hier ist ein Beispiel-Ausgang:SQL Server: row_number durch Timeout partitioniert

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) 

Spalten 1 und 2 sind Eingänge, 3 und 4 sind die gewünschten Ausgänge. Die Tabelle zeigt zwei Benutzer.

Da der zugrunde liegende Tabelle ist wirklich groß, Wie kann dies effizient gelöst werden? Ich würde eine kleine konstante Anzahl von Durchläufen über die Daten bevorzugen (ein oder zwei).

+0

Welche Version von SQL Server? Wenn 2012, wird die neue "OVER" -Klauselfunktionalität helfen. –

+0

Ja, es ist SQL Server 2012. – usr

Antwort

8

Hier ist ein paar versucht es.

;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 

Dies hat zwei Sortiervorgänge (von IP, DateTime dann durch IP, IPSessionNumber) aber nicht davon ausgehen, dass die SessionNumber beliebig, solange eine andere eindeutige Sitzungsnummer an jede neue Sitzung pro der IP-Adresse zugewiesen wird/30 Minuten zugeordnet werden kann Regel.

Um die SessionNumber s sequentiell in zeitlicher Reihenfolge zu übertragen. Ich habe folgendes benutzt.

;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 

Dies erhöht die Anzahl der Sortiervorgänge auf 4 jedoch.

+0

Wenn Anfragen von zwei IPs verschachteln, werden ihre Sitzungen nicht verwechseln? – Andomar

+0

@Andomar - Guter Punkt! Fest. –

+0

Eine Fensterzählung ist genial! Ich werde mich an diesen Trick erinnern. – usr

2

Hier ist eine Version einer Tabellenvariable und row_number mit einer ID zu erstellen, die in einer rekursiven CTE verwendet werden können. Es könnte sich lohnen, die Performance mit dem Cursor und einer Abfrage (von Martin bereitgestellt) zu vergleichen.

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

Ich mag diese Version, weil sie einfach zu erweitern ist, fast wie beim cursorbasierten Ansatz. Ich habe es geändert, um eine temporäre Tabelle zu verwenden, die ein Optimierungsproblem behob (Tabellenvariablen haben keine Statistiken). Außerdem habe ich überprüft, dass dieser Code funktioniert. Vielen Dank! – usr