Brightlane's retention team wants each customer's total spend on orders that actually went through — excluding any cancelled orders.
Write a query to return each customer's ID alongside their total spend across non-cancelled orders.
Assumptions:
- The
orderstable contains every order Brightlane has processed. - An order is cancelled if
status = 'cancelled'; every other status counts toward the customer's spend. - Cancelled orders do not contribute to any customer's per-customer total.
Output:
- One row per customer with at least one non-cancelled order, with columns
customer_idandtotal_spend.
Schema · ecommerce 5 tables
Run previews · Check grades
Write a query, then run it to see results here.
Worked solution Try it yourself first
SELECT
customer_id,
SUM(total_amount) AS total_spend
FROM
orders
WHERE
status <> 'cancelled'
GROUP BY
customer_id The shape
WHERE status <> 'cancelled' removes cancelled orders from the row set before any grouping happens. GROUP BY customer_id then partitions the survivors by buyer, and SUM(total_amount) adds up only the non-cancelled order amounts inside each partition. The cancelled orders never enter the sum at all, which is the correct meaning of "total spend on orders that actually went through."
Clause by clause
SELECT customer_id, SUM(total_amount) AS total_spendreturns the customer ID and their non-cancelled spend total.customer_idis inGROUP BY; the sum is the aggregate.FROM ordersis the full order history, cancelled orders included for now.WHERE status <> 'cancelled'runs before grouping. Every order whose status is'cancelled'is removed from the row set. Every other status survives, includingdelivered,shipped, andpending.GROUP BY customer_idpartitions the surviving rows by buyer.SUM(total_amount)runs once per buyer's bucket, adding up only the non-cancelled amounts.
Why this and not a per-status exclusion inside the sum
A learner might reach for SUM(total_amount) WHERE status <> 'cancelled' written inline, but that is not a valid SQL form. The filter and the aggregate are separate clauses, and they run in a fixed order: WHERE first, then GROUP BY, then aggregates. Putting the cancellation filter in WHERE is the canonical way to scope the sum to non-cancelled orders. Customer 17's non-cancelled total of 4845 reflects every delivered, shipped, and pending order they have, with cancelled amounts removed before the sum saw them.
You practiced filtering at the row level (WHERE) rather than at the group level (HAVING). 'Exclude cancelled orders' is a per-row condition, so it belongs in WHERE — not HAVING.