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

Return every return-visit session's `user_id`, `session_id`, `started_at`, and `gap_from_prev` — the time elapsed since that user's previous session began

Part of Sessionization and Funnel Analysis Patterns in SQL

The problem

Scenario: Streamhub's growth team defines a 'return visit' as a session that begins more than seven days after the user's previous session.

Task: Write a query to return every return-visit session's user_id, session_id, started_at, and gap_from_prev — the time elapsed since that user's previous session began.

Assumptions:

  • Within a user's history, sessions are ordered by started_at ascending.
  • A session's gap_from_prev is the difference between its started_at and the started_at of the user's immediately preceding session.
  • A return-visit session is one whose gap_from_prev is strictly greater than seven days.
  • The result covers only return-visit sessions.

Output:

  • One row per return-visit session.
  • Columns in this order: user_id, session_id, started_at, gap_from_prev.
  • Sorted by user_id ascending, then started_at ascending.
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
  session_gaps AS (
    SELECT
      id AS session_id,
      user_id,
      started_at,
      started_at - LAG(started_at) OVER (
        PARTITION BY
          user_id
        ORDER BY
          started_at
      ) AS gap_from_prev
    FROM
      sessions
  )
SELECT
  user_id,
  session_id,
  started_at,
  gap_from_prev
FROM
  session_gaps
WHERE
  gap_from_prev > INTERVAL '7 days'
ORDER BY
  user_id,
  started_at

The shape

A "return visit" is defined entirely by the gap between consecutive sessions for the same user, so computing that gap with LAG in a CTE and then filtering for gaps over seven days yields the return-visit set in one straight pass.

Clause by clause

  • The session_gaps CTE returns every session along with started_at - LAG(started_at) OVER (PARTITION BY user_id ORDER BY started_at) AS gap_from_prev. The partition keeps the look-back inside one user's history; the subtraction produces an interval per session. The user's first session has NULL here because there is no prior row inside the partition.
  • SELECT user_id, session_id, started_at, gap_from_prev FROM session_gaps WHERE gap_from_prev > INTERVAL '7 days' keeps only the sessions whose gap exceeds the seven-day threshold. Because NULL > INTERVAL '7 days' evaluates to NULL (not true), every user's first session is automatically excluded without any extra clause.
  • ORDER BY user_id, started_at produces the requested sort.

Why this and not a self-join on consecutive sessions

You could join sessions to itself on user_id, pair each session with one started earlier, take the maximum earlier-session started_at per outer session, and subtract. That reproduces the same gap, but it asks the planner to materialise every prior-session pair before reducing them. LAG does the same look-back in one window pass and gives you the gap directly on the row, which keeps the query a single linear read of the table.

The trap

The threshold is on a duration, not a date. Writing WHERE gap_from_prev > 7 compares an interval to an integer and either errors out or coerces in ways that vary by version. INTERVAL '7 days' is the right-hand side that lets the comparison stay in interval arithmetic the whole way through. The same care applies if the rule changes to "more than three hours" — the constant has to carry its unit.

You practiced computing per-user session gaps in a CTE, then keeping only sessions whose gap exceeds the seven-day re-engagement threshold.

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.