N016-H3 Tier 2 · Core SQL · hard analytics · Streamhub

Return every user's ID, plan, and tag

Part of CASE WHEN Expressions in SQL

The problem

Streamhub's analytics team needs to tag users by paid-plan tier. The mapping for paid plans is:

  • starter'entry'
  • pro'growth'
  • enterprise'enterprise'

Free-plan users should appear in the result with no tag — their tag is missing.

Write a query to return every user's ID, plan, and tag.

Assumptions:

  • The users table contains every account on the Streamhub platform.
  • The plan column has four values: free, starter, pro, and enterprise.
  • Every user appears in the output, including free-plan users — their tag is simply missing.

Output:

  • One row per user, with columns id, plan, and tag. Rows for free-plan users will have a missing tag.
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
  id,
  plan,
  CASE
    WHEN plan = 'starter' THEN 'entry'
    WHEN plan = 'pro' THEN 'growth'
    WHEN plan = 'enterprise' THEN 'enterprise'
  END AS tag
FROM
  users

The shape

Three WHEN branches cover the three paid plans, and there's no ELSE. When a free-plan user falls through every branch, CASE returns NULL by default — which is exactly what the prompt asks for: a missing tag on free-plan rows.

Clause by clause

  • SELECT id, plan returns the user's ID and raw plan value, so free-plan rows are visible in the result alongside their missing tag.
  • WHEN plan = 'starter' THEN 'entry', WHEN plan = 'pro' THEN 'growth', and WHEN plan = 'enterprise' THEN 'enterprise' are the three explicit branches. Each is an equality test against a specific paid-plan value; since the plan values are mutually exclusive, branch order doesn't matter.
  • There is no ELSE. For a free-plan user, none of the three WHEN conditions evaluate to true. PostgreSQL's defined behavior is to return NULL when no branch matches and no ELSE is present.
  • END AS tag closes the expression and labels the column. Free-plan rows have this column populated with NULL; paid-plan rows have the mapped string.
  • FROM users is the source set: every account on the platform, including free users.

Why this and not ELSE NULL

Writing the same query as ... ELSE NULL END AS tag returns the identical result — same rows, same values, same NULLs on free-plan users. The two forms are functionally interchangeable. The trade-off is readability: an explicit ELSE NULL makes the intent visible in the code ("free-plan users get nothing, on purpose"), while the omitted-ELSE form relies on the reader knowing the default behavior. For production work, the explicit form is the better habit.

The canonical query here drops the ELSE to demonstrate the default. Either form is correct; the explicit form is just more honest about what's happening.

The trap

The trap is reading an omitted ELSE as ELSE 0 or ELSE '' rather than ELSE NULL. A missing branch never returns a placeholder value — it returns the absence of a value. That distinction matters anywhere the CASE result feeds into something type-sensitive: an empty string and a NULL are different inputs, a zero and a NULL produce different downstream sums, and NULL = NULL is NULL, not true. The omitted ELSE is the cleanest way to say "no value applies," but it's also the easiest to misread as a defaulting behavior it doesn't have.

You practiced relying on CASE's default behavior when no WHEN branch matches and no ELSE is present. Writing an explicit ELSE NULL makes the intent visible.

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.