N027-H2 Tier 2 · Core SQL · hard ecommerce · Brightlane

Return all four columns per customer

Part of Conditional Aggregation (CASE inside Aggregates) in SQL

The problem

Brightlane's logistics team wants a per-customer order-status breakdown across three buckets:

  • pending_count — orders with status = 'pending'.
  • in_transit_count — orders with status = 'shipped'.
  • final_count — orders in any final state (status = 'delivered' or status = 'cancelled').

Write a query to return all four columns per customer.

Assumptions:

  • The orders table contains every order Brightlane has processed.
  • The three buckets are mutually exclusive and together cover every status value — every order contributes to exactly one of the three columns.
  • The final_count bucket combines two statuses with an OR inside the CASE predicate.

Output:

  • One row per customer with at least one order, with columns customer_id, pending_count, in_transit_count, and final_count.
Schema · ecommerce 5 tables
categories
id integer
name text
parent_id? integer
products
id integer
name text
category_id integer
price numeric
stock_qty integer
attributes? jsonb
order_items
id integer
order_id integer
product_id integer
quantity integer
unit_price numeric
customers
id integer
name text
email text
city? text
country text
created_at timestamptz
is_active boolean
orders
id integer
customer_id integer
ordered_at timestamptz
status text
total_amount numeric

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution Try it yourself first
Solution query
SELECT
  customer_id,
  COUNT(
    CASE
      WHEN status = 'pending' THEN 1
    END
  ) AS pending_count,
  COUNT(
    CASE
      WHEN status = 'shipped' THEN 1
    END
  ) AS in_transit_count,
  COUNT(
    CASE
      WHEN status = 'delivered'
      OR status = 'cancelled' THEN 1
    END
  ) AS final_count
FROM
  orders
GROUP BY
  customer_id

The shape

Three conditional counts split each customer's orders into pending, in-transit, and final-state buckets. The final_count predicate uses OR inside the CASE so that two raw status values ('delivered' and 'cancelled') consolidate into one report column. Customer 1 has 5 final-state orders and nothing in transit or pending; customer 22 has 1 pending and 2 final-state.

Clause by clause

  • customer_id is the grouping column; each customer's orders form one per-customer group.
  • COUNT(CASE WHEN status = 'pending' THEN 1 END) AS pending_count and COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS in_transit_count each test a single status literal. The 'shipped' rows feed the in-transit count; the prompt's column name maps the logistics team's vocabulary onto the database's status value.
  • COUNT(CASE WHEN status = 'delivered' OR status = 'cancelled' THEN 1 END) AS final_count consolidates two statuses into one bucket. The OR operator combines two equality checks into a single boolean expression. The CASE returns 1 for any row whose status matches either side; everything else falls through to NULL and COUNT skips it.
  • FROM orders GROUP BY customer_id partitions the rows per customer before the aggregates run.

Why OR and not two separate COUNT(CASE) calls

The alternative would be COUNT(CASE WHEN status = 'delivered' THEN 1 END) + COUNT(CASE WHEN status = 'cancelled' THEN 1 END). Same number, more moving parts. The OR form expresses the report's logic directly: these two raw statuses are one bucket. If 'returned' later becomes a final state, the OR form adds one term inside one predicate; the addition form needs a third COUNT(CASE) and a re-balanced sum.

The trap

OR binds looser than AND, so a WHEN such as status = 'delivered' OR status = 'cancelled' AND customer_id > 50 reads, with default precedence, as status = 'delivered' OR (status = 'cancelled' AND customer_id > 50) rather than (status = 'delivered' OR status = 'cancelled') AND customer_id > 50. The query runs without error and returns a plausible-looking count, but it has counted every delivered row regardless of customer_id instead of just the high-ID final-state rows.

The rule is the same one from WHERE: when OR and AND share a predicate, parentheses around the OR-group are mandatory. They cost nothing to add and they eliminate the ambiguity at the source.

You practiced a CASE predicate that combines two statuses with OR to produce a single bucket. The recurring shape: any time multiple raw values map to one report bucket, the boolean expression inside CASE does the consolidation — the surrounding COUNT(CASE) scaffold doesn't change.

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.