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

Return the customer ID and the average delivered-order value for every customer who has any orders on file

Part of Conditional Aggregation (CASE inside Aggregates) in SQL

The problem

Brightlane's finance team needs per-customer average order values, computed only against the customer's fulfilled orders.

Write a query to return the customer ID and the average delivered-order value for every customer who has any orders on file.

Assumptions:

  • The orders table contains every order Brightlane has processed; a delivered order has status = 'delivered'.
  • Every customer with any orders should appear in the result — even customers whose orders are all non-delivered.
  • For a customer with zero delivered orders, the average must be missing, not 0 — there is no delivered-order data to average for that customer.

Output:

  • One row per customer with any orders, with columns customer_id and avg_delivered_value. Customers with no delivered orders will have a missing value in the second column.
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,
  AVG(
    CASE
      WHEN status = 'delivered' THEN total_amount
    END
  ) AS avg_delivered_value
FROM
  orders
GROUP BY
  customer_id

The shape

AVG(CASE WHEN status = 'delivered' THEN total_amount END) computes a per-customer average over only the delivered orders in each group. The other orders contribute NULL from the CASE, and AVG excludes NULL from both the sum and the count. Customers with at least one delivered order get a real average; customers 22, 5, and 13 — whose orders are all non-delivered — get NULL, because there were no delivered amounts to average.

Clause by clause

  • customer_id is the grouping column. Every customer with any orders becomes one group, regardless of whether those orders are delivered.
  • AVG(CASE WHEN status = 'delivered' THEN total_amount END) AS avg_delivered_value evaluates the CASE once per row. Delivered rows contribute their total_amount to the input set for AVG; non-delivered rows fall through to NULL and are skipped. The result is the average of the delivered amounts within that customer's group — the customer's typical fulfilled-order value, ignoring their pending, shipped, and cancelled orders.
  • FROM orders GROUP BY customer_id partitions every order into per-customer groups before the aggregate runs.

Why this and not WHERE status = 'delivered'

Filtering with WHERE status = 'delivered' before the GROUP BY would produce the same average for customers who have delivered orders. But it would drop customers 22, 5, and 13 from the output entirely — WHERE removes their rows before grouping, so they form no group. The prompt is explicit that every customer with any orders has to appear.

Conditional aggregation keeps the full per-customer group intact and lets the CASE decide which rows feed AVG. The group still exists for a customer with zero delivered orders; the input set for AVG inside that group is just empty.

The trap

The trap is reading the NULL averages as a query bug. They are the structurally correct answer for "average of an empty set." Customer 22 has three orders, all non-delivered; there is no delivered-order total to average. 0 would be wrong — that would imply Brightlane delivered orders to customer 22 with an average value of zero dollars. NULL says correctly that there is no delivered-order data for that customer.

When a downstream report needs a defined number instead of NULL, the right place to convert it is at the report boundary, not inside the CASE. The aggregate's NULL carries real information about which customers have no fulfilled orders.

You practiced AVG(CASE) where some groups have no matching rows. When the conditional input is missing for every row in the group, the average is missing too — the right answer for 'no data'.

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.