N046-M4 Tier 4 · Advanced · medium analytics · Streamhub

Return one row per session with at least one event, showing the session ID, ID of the most recent event in that session, the event type, and when the event occurred. Sort the final result by `session_id` ascending

Part of DISTINCT ON in SQL

The problem

Streamhub's analytics team is reviewing the last recorded event for each session.

Write a query to return one row per session with at least one event, showing the session ID, ID of the most recent event in that session, the event type, and when the event occurred. Sort the final result by session_id ascending.

Assumptions:

  • A session's most recent event is the event with the largest occurred_at for that session_id.
  • Sessions with no events on record do not appear in the result.
  • The final result is sorted by session_id ascending.

Output:

  • One row per session with at least one event, with columns session_id, event_id, event_type, and occurred_at. Sorted by session_id.
Schema · analytics 5 tables
users
id integer
name text
email text
country text
plan text
signed_up_at timestamptz
is_active boolean
conversions
id integer
user_id integer
converted_at timestamptz
plan text
amount numeric
sessions
id integer
user_id integer
started_at timestamptz
ended_at? timestamptz
event_count integer
events
id integer
user_id integer
session_id? integer
event_type text
occurred_at timestamptz
properties? jsonb
periods
id integer
name text
start_month integer
end_month integer

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution Try it yourself first
Solution query
SELECT DISTINCT
  ON (session_id) session_id,
  id AS event_id,
  event_type,
  occurred_at
FROM
  events
ORDER BY
  session_id,
  occurred_at DESC

The shape

DISTINCT ON (session_id) keeps one row per session, and ORDER BY session_id, occurred_at DESC picks the last event recorded in each session. The pattern is the same latest-per-group shape used for latest-per-customer, applied to a different schema's primary entity relationship: sessions contain events, and the review wants the freshest event per session.

Clause by clause

  • SELECT DISTINCT ON (session_id) session_id, id AS event_id, event_type, occurred_at returns the four columns the analytics review needs. DISTINCT ON (session_id) declares the deduplication key: one row per distinct session_id. The id AS event_id alias names the event identifier column.
  • FROM events reads the event records. Sessions with no events never enter this row source, so they cannot appear in the result.
  • ORDER BY session_id, occurred_at DESC sorts the events so that within each session's group, the most recent event sits first. PostgreSQL keeps the first row in each session_id group, which is that session's last event. The leading session_id ascending also gives the final result the session-ordered shape the prompt asks for.

Why this and not ROW_NUMBER

The same pick is reachable with a window function:

SELECT session_id, event_id, event_type, occurred_at
FROM (
  SELECT session_id, id AS event_id, event_type, occurred_at,
    ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY occurred_at DESC) AS rn
  FROM events
) ranked
WHERE rn = 1
ORDER BY session_id

Both return the same rows. DISTINCT ON says the intent in one clause; ROW_NUMBER says it across a subquery, a window function, and a filter. On event-stream tables where the latest-per-key pattern is common, DISTINCT ON keeps the query readable as one thought.

You practiced DISTINCT ON over an event-stream table — same per-group-latest shape, applied to a different schema's primary entity relationship.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.

Practice, feedback, mastery, review. That's the loop that turns reading into real skill.

Start free

No account, no credit card. Start solving in under a minute.