First, your requirement can be interpreted more than one way so let me stat what I think you are wanting... I think you are saying a session ends when a given IP address has 30 minutes of inactivity. So if an IP address hits a site every minute for 2 hours and then takes a 30 minute break that represents one session. Assuming that is what you intended...
You can use LEAD and LAG to help you identify sessions. My test data consisted of an Id column, an IPAddress column, and a Created column. Here is the code, explanation follows...
WITH t1 AS
(
SELECT
*
, DATEDIFF(minute, LAG(Created, 1, 0) OVER (PARTITION BY IPAddress ORDER BY Created), [Created]) AS SinceLastAccess
FROM
IISLog
), sessionStarts AS
(
SELECT
*
FROM
t1
WHERE
SinceLastAccess >= 30
), sessionInfo AS
(
SELECT
IPAddress
, Created AS SessionStart
, LEAD(Created, 1, '2025-01-01') OVER (PARTITION BY IPAddress ORDER BY CREATED) AS SessionEnd
FROM
sessionStarts
)
SELECT * FROM sessionInfo
The first CTE (t1) selects the data, but adds a column called SinceLastAccess
. This new column uses the LAG function to look at the value in the previous row and calculate how many minutes have passed. The PARTITION BY
constrains this calculation to each IP address.
The second CTE (sessionStarts) simply selects those rows from t1 where the SinceLastAccess
value is greater than 30. This effectively tells us the beginning of every session.
Finally, the `sessionInfo' CTE builds upon the second. Using the LEAD function we look forward to see where the next session begins. This value is taken to be when the current row's session ends. What we finally end up with is an IP address, session start, and session end. Now that you have these, it should be easy to JOIN this to the original table and GROUP it up.