N065-M3 Tier 5 · Expert · medium analytics · Streamhub

Return two counts: the number of users who performed at least one `'page_view'` event (`reached_step_1`), and the number of those users who later performed at least one `'upgrade_clicked'` event after their first `'page_view'` (`reached_step_2`)

Part of Sessionization and Funnel Analysis Patterns in SQL

The problem

Scenario: Streamhub's growth team is measuring conversion from initial product exploration to upgrade intent.

Task: Write a query to return two counts: the number of users who performed at least one 'page_view' event (reached_step_1), and the number of those users who later performed at least one 'upgrade_clicked' event after their first 'page_view' (reached_step_2).

Assumptions:

  • A user reaches step 1 by performing at least one 'page_view' event at any time.
  • A user reaches step 2 by performing at least one 'upgrade_clicked' event strictly after their earliest 'page_view'.
  • Every user who reaches step 2 also reaches step 1.

Output:

  • One row, holding the two step counts.
  • Columns in this order: reached_step_1, reached_step_2.
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
WITH
  step_1 AS (
    SELECT
      user_id,
      MIN(occurred_at) AS step_1_time
    FROM
      events
    WHERE
      event_type = 'page_view'
    GROUP BY
      user_id
  ),
  upgrade_events AS (
    SELECT
      user_id,
      occurred_at
    FROM
      events
    WHERE
      event_type = 'upgrade_clicked'
  ),
  step_2 AS (
    SELECT
      ue.user_id,
      MIN(ue.occurred_at) AS step_2_time
    FROM
      upgrade_events ue
      JOIN step_1 s ON s.user_id = ue.user_id
    WHERE
      (ue.occurred_at - s.step_1_time) > INTERVAL '0 seconds'
    GROUP BY
      ue.user_id
  )
SELECT
  (
    SELECT
      COUNT(*)
    FROM
      step_1
  ) AS reached_step_1,
  COUNT(*) AS reached_step_2
FROM
  step_2

The shape

A two-step funnel is two stacked CTEs: the first finds each user's earliest qualifying event for step 1, the second restricts to users who reached step 1 and finds their earliest step-2 event that happens strictly after their step-1 time. Counting users in each CTE gives the conversion numbers.

Clause by clause

  • step_1 selects user_id, MIN(occurred_at) AS step_1_time FROM events WHERE event_type = 'page_view' GROUP BY user_id. Filtering before aggregating means MIN runs only over the qualifying event type, so each user's step-1 time is their earliest 'page_view'.
  • upgrade_events is a thin staging CTE that pre-filters events down to the rows where event_type = 'upgrade_clicked', which keeps the next join small.
  • step_2 joins upgrade_events to step_1 on user_id so only users who reached step 1 are considered, then filters with (ue.occurred_at - s.step_1_time) > INTERVAL '0 seconds' to keep only upgrade clicks that happened strictly after the user's first 'page_view'. MIN(ue.occurred_at) per user then picks each user's earliest qualifying upgrade.
  • The final SELECT (SELECT COUNT(*) FROM step_1) AS reached_step_1, COUNT(*) AS reached_step_2 FROM step_2 uses a scalar subquery for the step-1 count and counts rows of step_2 for step 2. Both CTEs are already one row per user, so a plain COUNT(*) is the per-user count.

The trap

The time-ordering constraint is the difference between a real funnel and a count of who ever did each event. Dropping (ue.occurred_at - s.step_1_time) > INTERVAL '0 seconds' would let a user who clicked upgrade before they ever viewed a page count as converted, which inflates step 2 with backwards journeys. The strict greater-than is what makes it a funnel.

You practiced staging a two-step funnel as chained CTEs — earliest qualifying event per user at step 1, then qualifying events at step 2 that occur after each user's step-1 timestamp.

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.