runtime
54 / 88

lesson 54 Advanced product analysis · advanced

Sessionize an event stream

Group consecutive events into user sessions.

the idea

Sessionization groups each user’s events into sessions: a new session starts at the first event or after a gap of at least 30 minutes. Mark starts with LAG, then turn the marks into session numbers with a running SUM.

Dataset: Product / SaaS Analytics

The tables the challenges use, with their first 6 rows. Under each column name: the kind of value it holds, and which table it links to.

sessions5 columns · 47,891 rows
session_idnumber · unique iduser_idnumber · links to usersstarted_atdate & timeended_atdate & timedevicetext
112024-01-14 18:52:142024-01-14 19:01:14web
212024-01-21 11:24:152024-01-21 11:37:15web
322024-01-24 03:43:142024-01-24 03:46:14android
412024-02-02 07:45:532024-02-02 08:22:53web
532024-02-02 21:34:252024-02-02 21:49:25web
642024-03-31 03:39:322024-03-31 03:44:32web
session_idnumber · unique id
1
user_idnumber · links to users
1
started_atdate & time
2024-01-14 18:52:14
ended_atdate & time
2024-01-14 19:01:14
devicetext
web
session_idnumber · unique id
2
user_idnumber · links to users
1
started_atdate & time
2024-01-21 11:24:15
ended_atdate & time
2024-01-21 11:37:15
devicetext
web
session_idnumber · unique id
3
user_idnumber · links to users
2
started_atdate & time
2024-01-24 03:43:14
ended_atdate & time
2024-01-24 03:46:14
devicetext
android
session_idnumber · unique id
4
user_idnumber · links to users
1
started_atdate & time
2024-02-02 07:45:53
ended_atdate & time
2024-02-02 08:22:53
devicetext
web
session_idnumber · unique id
5
user_idnumber · links to users
3
started_atdate & time
2024-02-02 21:34:25
ended_atdate & time
2024-02-02 21:49:25
devicetext
web
session_idnumber · unique id
6
user_idnumber · links to users
4
started_atdate & time
2024-03-31 03:39:32
ended_atdate & time
2024-03-31 03:44:32
devicetext
web

first 6 of 47,891 rows · your queries use all of them

events8 columns · 135,428 rows
delivery_idnumber · unique idevent_idtextuser_idnumber · links to usersaccount_idnumber · links to accountssession_idnumber · links to sessionsevent_nametextoccurred_atdate & timereceived_atdate & time
1evt_0000001111user_signed_up2024-01-14 18:52:142024-01-14 18:52:33
2evt_0000002111onboarding_started2024-01-14 18:52:512024-01-14 18:53:06
3evt_00000846232user_signed_up2024-01-15 13:21:522024-01-15 13:22:00
4evt_00000856232onboarding_started2024-01-15 13:22:052024-01-15 13:22:16
5evt_00000866232onboarding_completed2024-01-15 13:23:522024-01-15 13:24:12
6evt_0000019119search_performed2024-01-17 20:56:552024-01-17 20:56:59
delivery_idnumber · unique id
1
event_idtext
evt_0000001
user_idnumber · links to users
1
account_idnumber · links to accounts
1
session_idnumber · links to sessions
1
event_nametext
user_signed_up
occurred_atdate & time
2024-01-14 18:52:14
received_atdate & time
2024-01-14 18:52:33
delivery_idnumber · unique id
2
event_idtext
evt_0000002
user_idnumber · links to users
1
account_idnumber · links to accounts
1
session_idnumber · links to sessions
1
event_nametext
onboarding_started
occurred_atdate & time
2024-01-14 18:52:51
received_atdate & time
2024-01-14 18:53:06
delivery_idnumber · unique id
3
event_idtext
evt_0000084
user_idnumber · links to users
6
account_idnumber · links to accounts
2
session_idnumber · links to sessions
32
event_nametext
user_signed_up
occurred_atdate & time
2024-01-15 13:21:52
received_atdate & time
2024-01-15 13:22:00
delivery_idnumber · unique id
4
event_idtext
evt_0000085
user_idnumber · links to users
6
account_idnumber · links to accounts
2
session_idnumber · links to sessions
32
event_nametext
onboarding_started
occurred_atdate & time
2024-01-15 13:22:05
received_atdate & time
2024-01-15 13:22:16
delivery_idnumber · unique id
5
event_idtext
evt_0000086
user_idnumber · links to users
6
account_idnumber · links to accounts
2
session_idnumber · links to sessions
32
event_nametext
onboarding_completed
occurred_atdate & time
2024-01-15 13:23:52
received_atdate & time
2024-01-15 13:24:12
delivery_idnumber · unique id
6
event_idtext
evt_0000019
user_idnumber · links to users
1
account_idnumber · links to accounts
1
session_idnumber · links to sessions
9
event_nametext
search_performed
occurred_atdate & time
2024-01-17 20:56:55
received_atdate & time
2024-01-17 20:56:59

first 6 of 135,428 rows · your queries use all of them

worked example

Run these steps in order to see the idea before you write your own query. The step marked “the pattern” is what the challenges ask for. Running the example does not count toward progress.

  1. Measure idle time between events. Run this. LAG() on occurred_at shows minutes since user 100’s previous event—the input to a 30-minute session rule.

    step-1.sql
    SELECT
      user_id,
      occurred_at,
      ROUND((julianday(occurred_at) - julianday(LAG(occurred_at) OVER (PARTITION BY user_id ORDER BY occurred_at, event_id))) * 1440, 1) AS minutes_since_previous
    FROM events
    WHERE user_id = 100;
  2. Compare to stored sessions. Run this. The sessions table is the product’s own log for the same user—your SQL rebuild should resemble these boundaries.

    step-2.sql
    SELECT session_id, started_at, ended_at
    FROM sessions
    WHERE user_id = 100;
  3. Mark where a session starts. Run this. starts_session is 1 on the first event or after a gap of at least 30 minutes; a running sum of those flags numbers sessions next.

    step-3.sql
    WITH g AS (
      SELECT
        user_id,
        occurred_at,
        (julianday(occurred_at) - julianday(LAG(occurred_at) OVER (PARTITION BY user_id ORDER BY occurred_at, event_id))) * 1440 AS gap
      FROM events
      WHERE user_id = 100
    )
    SELECT
      occurred_at,
      ROUND(gap, 1) AS minutes_since_previous,
      CASE
        WHEN gap IS NULL OR gap >= 30 THEN 1
        ELSE 0
      END AS starts_session
    FROM g;
  4. Put it together. The lesson's pattern in one query.the pattern

    example.sql
    SELECT
      user_id,
      occurred_at,
      LAG(occurred_at) OVER (PARTITION BY user_id ORDER BY occurred_at, event_id) AS previous_at
    FROM events
    WHERE user_id = 100;

common mistake

Ordering by received_at instead of occurred_at splits sessions when late deliveries arrive out of order.

challenge 1

Using one delivery per event_id for events in June 2025, return user_id and sessions: the number of sessions per user, where a session starts at the user’s first event or after a gap of 30 minutes or more (ordered by occurred_at, event_id).

what to return

Columns
user_idsessions in this order
Rows
any order
challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.
Sessionize an event stream — Advanced product analysis · Runtime