N020-H2 Tier 2 · Core SQL · hard analytics · Streamhub

Return the user ID, session ID, and period name for every (session × period) combination

Part of CROSS JOIN in SQL

The problem

Streamhub's data engineering team wants a scaffold for per-period session analytics: one row per user session per reporting period. Each existing session must be expanded across all four quarterly periods.

Write a query to return the user ID, session ID, and period name for every (session × period) combination.

Assumptions:

  • There are exactly four reporting quarters.
  • The result combines two operations: each session is matched to its user (every session has exactly one user), and each session is expanded across all four quarterly periods.

Output:

  • One row per session-period combination, with columns user_id, session_id, and period_name.
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
  u.id AS user_id,
  s.id AS session_id,
  p.name AS period_name
FROM
  users u
  CROSS JOIN periods p
  JOIN sessions s ON s.user_id = u.id

The shape

Two joins do two different jobs in the same query. JOIN sessions s ON s.user_id = u.id matches each user to their actual sessions — a real relationship in the data. CROSS JOIN periods p then expands the result across all four quarters, unconditionally. The output is one row per existing session per quarter — the scaffold the data engineering team needs for per-period analytics.

Clause by clause

  • FROM users u CROSS JOIN periods p produces the user-by-period grid first: every user paired with every quarter, no condition.
  • JOIN sessions s ON s.user_id = u.id then matches that intermediate result against sessions on the user-ID link. Each session has exactly one user, so a session attached to user 1 joins onto the four (user 1, period) rows already in the intermediate result. Every session ends up appearing four times — once paired with each quarter.
  • u.id AS user_id reads the user ID from the users side. s.id AS session_id reads the session ID. p.name AS period_name reads the quarter label. Three columns, three source tables, one per column.

Why this and not sessions CROSS JOIN periods

SELECT s.user_id, s.id AS session_id, p.name FROM sessions s CROSS JOIN periods p would return the same rows in this dataset, because sessions.user_id already carries the link to users and you don't need to materialise the users row to read the session-period grid. The query in front of you keeps users in the FROM because the scaffold's user ID is being read from u.id rather than s.user_id — same value either way, different source column. Both shapes work; the longer one makes the role of the user table explicit.

The trap

The trap is mentally collapsing the two joins into one and trying to relate sessions to periods directly. JOIN periods p ON ... would require a match condition between sessions and periods, and there is no such column — sessions don't carry a period reference. The relationship the report needs is not a match; it's a complete expansion. CROSS JOIN does that expansion without any condition, and the INNER JOIN to sessions then attaches the real-data side. Two different join types in the same query because two different jobs are happening: matching where data already links, expanding where it doesn't.

You practiced combining CROSS JOIN and INNER JOIN in the same query. Each does its own job — matching on a condition for one, producing every pairing for the other — and they compose freely in the same FROM clause.

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.