N066-M4 Tier 5 · Expert · medium ecommerce · Brightlane

Return each customer's `id`, name, and `total_revenue` — the combined line-item value across all of their `orders`, reported as a missing value for customers who have no orders on record

Part of Analyst Debugging Patterns in SQL

The problem

Scenario: Brightlane's customer revenue pipeline composes line-item totals through orders up to customers. An analyst suspects some customers report a missing-value revenue because the chain is excluding customers with no orders. The diagnostic surfaces every customer's revenue alongside an explicit missing-value indicator for those with no orders on record.

Task: Write a query to return each customer's id, name, and total_revenue — the combined line-item value across all of their orders, reported as a missing value for customers who have no orders on record.

Assumptions:

  • A line item's value is quantity multiplied by unit_price.
  • A customer's total_revenue is the combined line-item value across every order they have placed, reported as a missing value when they have no orders on record.
  • The result covers every customer.

Output:

  • One row per customer.
  • Columns in this order: customer_id, customer_name, total_revenue.
  • Sorted by customer_name ascending.
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
WITH
  order_totals AS (
    SELECT
      order_id,
      SUM(quantity * unit_price) AS item_total
    FROM
      order_items
    GROUP BY
      order_id
  )
SELECT
  c.id AS customer_id,
  c.name AS customer_name,
  SUM(ot.item_total) AS total_revenue
FROM
  customers c
  LEFT JOIN orders o ON o.customer_id = c.id
  LEFT JOIN order_totals ot ON ot.order_id = o.id
GROUP BY
  c.id,
  c.name
ORDER BY
  c.name

The shape

A CTE that collapses line items to one row per order, then a left-join chain from customers outward through orders and into the order-total CTE. The chain stays left at every level, so a customer with no orders carries a NULL through to total_revenue instead of dropping out — the diagnostic signal that locates which layer introduced the gap.

Clause by clause

  • WITH order_totals AS (SELECT order_id, SUM(quantity * unit_price) AS item_total FROM order_items GROUP BY order_id) aggregates line items per order. One row per order, with the combined line-item value. This collapses the many side of the order-to-line-item relationship before any join to the customer side runs.
  • SELECT c.id AS customer_id, c.name AS customer_name, SUM(ot.item_total) AS total_revenue returns one row per customer. SUM(ot.item_total) totals the per-order subtotals; for a customer with no orders, every ot.item_total in the group is NULL and SUM of all NULLs returns NULL.
  • FROM customers c LEFT JOIN orders o ON o.customer_id = c.id keeps every customer regardless of whether they have orders. A customer with no orders gets one row with o.id set to NULL.
  • LEFT JOIN order_totals ot ON ot.order_id = o.id keeps the customer row even when o.id is NULL (the second left join propagates the preserved row through the second layer). For customers with orders, this attaches each order's item total; for customers without, ot.item_total is NULL.
  • GROUP BY c.id, c.name collapses back to one row per customer, with SUM running over the joined item totals.
  • ORDER BY c.name sorts alphabetically. The reference shows Cole Wood and several others with total_revenue of null — those are the customers with no orders, which is the exact diagnostic signal the prompt is checking for.

The trap

Switch any link in the chain to an INNER JOIN and the customers with no orders silently disappear from the result entirely. The output looks reasonable — a list of customers with revenue totals — but the rows the diagnostic was designed to surface are gone. The whole point of the left-join chain is to keep the parent on screen so the missing-value column tells the story; converting any join in the chain to inner removes that signal without raising an error. When a downstream filter or join could plausibly drop the row the diagnostic is hunting for, every join from the driving table outward has to stay LEFT JOIN for the full chain to remain leak-proof.

You practiced a left-join chain that preserves the parent at every level — customers with no orders surface with a missing-value revenue rather than dropping out, the diagnostic signal that locates the gap-introducing layer.

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.