N023-M3 Tier 2 · Core SQL · medium analytics · Streamhub

- `'session'` for rows from the `sessions` table. - `'conversion'` for rows from the `conversions` table

Part of UNION, UNION ALL, INTERSECT, EXCEPT in SQL

The problem

Streamhub's analytics team is building a unified activity feed combining session starts and conversion events.

Write a query to return each user ID alongside a label indicating which source the row came from:

  • 'session' for rows from the sessions table.
  • 'conversion' for rows from the conversions table.

Assumptions:

  • Both queries must produce two columns — the user ID and a string-literal source label — in the same column positions.
  • Duplicates are preserved (a user with three sessions and two conversions contributes five rows).

Output:

  • One row per source record across both tables, with columns user_id and activity_type.
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
  user_id,
  'session' AS activity_type
FROM
  sessions
UNION ALL
SELECT
  user_id,
  'conversion' AS activity_type
FROM
  conversions

The shape

A string literal in each query's SELECT list tags every row with the table it came from. After UNION ALL stacks the two sides, every row in the combined feed carries 'session' or 'conversion' in the activity_type column, so the analytics team can read the row's origin without going back to the source tables.

Clause by clause

  • SELECT user_id, 'session' AS activity_type FROM sessions is the left input. The literal 'session' doesn't depend on the row, so the column is effectively a constant tag on every session row. The AS activity_type alias names the column; the output column name comes from the left query, so this is the alias that survives into the combined result.
  • UNION ALL stacks the two result sets without deduplication. Every session row appears, then every conversion row, and rows that share the same user_id across the two sides both appear — which is what an activity feed needs.
  • SELECT user_id, 'conversion' AS activity_type FROM conversions is the right input. The literal 'conversion' tags every row on this side. The alias here is ignored — only the column position matters for the set operation.
  • The two queries project the same column count (two) and compatible types (integer + text on both sides), which is what allows the set operation to run.

Why this and not run two queries

The team could run SELECT user_id FROM sessions and SELECT user_id FROM conversions separately and add the source label in the reporting layer. That pushes the provenance logic out of the query and into application code, where it has to be re-applied every time the feed is rebuilt. Tagging at the source makes the combined result self-describing: any consumer can read activity_type and know which table the row came from.

You practiced adding a constant column to each side of a UNION ALL to tag source provenance. The recurring shape any time multiple structurally similar tables feed a unified report — the literal labels make the row's origin explicit.

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.