Example 1
Split each user's event stream into sessions using a 30-second inactivity threshold
User 1's events at timestamps 0, 10, and 20 are all within 30 seconds of each other, so they belong to session 1. The event at timestamp 2000 is 1980 seconds after the previous event — well above the 30-second threshold — so it starts session 2. User 2's two events (100 and 110) are 10 seconds apart and form a single session 1. The lagged CTE computes the previous timestamp per user with LAG. The flagged CTE converts each row into a 0/1 boundary flag: 1 for the first event per user (prev_ts IS NULL) or any event where the gap exceeds 30 seconds, 0 otherwise. The outer query computes a running sum of that flag — SUM(is_new_session) OVER (PARTITION BY user_id ORDER BY ts) — which starts at 1 (the first session boundary) and increments each time a new session begins, giving every event a stable session ID. To compute per-session statistics such as duration or event count, wrap this query in a CTE and group by (user_id, session_id).
CREATE TABLE user_events (user_id INT, ts INT);
INSERT INTO
user_events
VALUES
(1, 0),
(1, 10),
(1, 20),
(1, 2000),
(2, 100),
(2, 110);WITH
lagged AS (
SELECT
user_id,
ts,
LAG (ts) OVER (
PARTITION BY
user_id
ORDER BY
ts
) AS prev_ts
FROM
user_events
),
flagged AS (
SELECT
user_id,
ts,
CASE
WHEN prev_ts IS NULL
OR ts - prev_ts > 30 THEN 1
ELSE 0
END AS is_new_session
FROM
lagged
)
SELECT
user_id,
ts,
SUM(is_new_session) OVER (
PARTITION BY
user_id
ORDER BY
ts
) AS session_id
FROM
flagged
ORDER BY
user_id,
ts;| user_id | ts | session_id |
|---|---|---|
| 1 | 0 | 1 |
| 1 | 10 | 1 |
| 1 | 20 | 1 |
| 1 | 2000 | 2 |
| 2 | 100 | 1 |
| 2 | 110 | 1 |
Output is identical across all supported engine versions.