Scenario: Streamhub's UX team is mapping event progression within sessions to understand common user paths.
Task: Write a query to return each event's id, the session_id it belongs to, its event_type, its occurred_at timestamp, and the event_type of the next event within the same session.
Assumptions:
- Within a session, events are ordered by
occurred_atascending. - An event's
next_event_typeis theevent_typeof the event that immediately follows it within the same session. - The last event in each session has no following event within that session; its
next_event_typeis reported as a missing value. - Each event's
next_event_typeis drawn solely from events within the same session.
Output:
- One row per recorded event.
- Columns in this order:
event_id,session_id,event_type,occurred_at,next_event_type. - Sorted by
session_idascending, thenoccurred_atascending.
Schema · analytics 5 tables
Run previews · Check grades
Write a query, then run it to see results here.
Worked solution Try it yourself first
SELECT
id AS event_id,
session_id,
event_type,
occurred_at,
LEAD(event_type) OVER (
PARTITION BY
session_id
ORDER BY
occurred_at
) AS next_event_type
FROM
events
ORDER BY
session_id,
occurred_at The shape
LEAD(event_type) OVER (PARTITION BY session_id ORDER BY occurred_at) looks one row forward inside the same session and pulls that next event's event_type onto the current row. The partition keeps the look-ahead contained to one session, so the last event in a session correctly reports no following event.
Clause by clause
SELECT id AS event_id, session_id, event_type, occurred_atreturns the four identifying columns the prompt requests, in order, with the primary key renamed toevent_id.LEAD(event_type) OVER (PARTITION BY session_id ORDER BY occurred_at) AS next_event_typeis the mirror ofLAG. WhereLAGreaches back,LEADreaches forward by one row inside the same partition. For event 1 in session 1, the next event is afeature_used, sonext_event_typeis'feature_used'.FROM eventsreads the full event log.ORDER BY session_id, occurred_atproduces the requested sort and matches the window's reading order so each next-event value sits beside the row it belongs to.
The trap
LEAD without PARTITION BY session_id would happily reach into the next session and report its first event as the current session's "next event." There's no error; the value just lies about the user's flow. Partitioning by session_id restarts the window at every session boundary, which is what makes the last event in each session correctly return NULL for next_event_type instead of borrowing from a different session.
You practiced using LEAD partitioned by session_id to attach the next event's type alongside the current event — a forward-looking lookup that resets at each session boundary.