Brightlane's fulfilment team is reviewing high-volume customers for a dedicated-account programme.
Write a query to return the customer ID and count of delivered orders for every customer with more than two fulfilled orders.
Assumptions:
- The
orderstable contains every order Brightlane has processed. - A delivered order has
status = 'delivered'; only delivered orders should be counted. - The status condition is a per-row qualifier (rejecting non-delivered rows before the count runs); the
> 2threshold is a per-group qualifier (rejecting customers whose delivered count is too low).
Output:
- One row per qualifying customer, with columns
customer_idanddelivered_count.
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,
COUNT(*) AS delivered_count
FROM
orders
WHERE
status = 'delivered'
GROUP BY
customer_id
HAVING
COUNT(*) > 2 The shape
Two filters at two different stages. WHERE status = 'delivered' is a per-row qualifier — it eliminates non-delivered orders before any counting happens. HAVING COUNT(*) > 2 is a per-group qualifier — it eliminates customers whose surviving delivered-order count is too low. Together they answer "customers with more than two delivered orders" rather than "customers with more than two orders of any kind."
Clause by clause
SELECT customer_id, COUNT(*) AS delivered_countreturns each qualifying customer's id alongside their count of delivered orders. TheCOUNT(*)operates on the filtered, grouped row set, not the fullorderstable.FROM ordersis the source set.WHERE status = 'delivered'runs first. Every order whosestatusis anything other than'delivered'drops out before the next clause sees it. The working set after this filter contains only delivered orders.GROUP BY customer_idpartitions the surviving rows by customer. Each group's row count is the customer's number of delivered orders.HAVING COUNT(*) > 2filters those groups. Customers with two or fewer delivered orders drop out; three or more survive.customer_id1has five delivered orders and qualifies; a customer whose only orders are cancelled never enters the grouping at all.
Why this and not all in HAVING
Both filters could technically live in HAVING. HAVING status = 'delivered' AND COUNT(*) > 2 is valid SQL, but it forces PostgreSQL to group every customer's entire order history first and then discard the non-delivered rows after the fact. Pushing the status filter into WHERE removes those rows before grouping, so the groups are smaller and the count is correct on the first pass.
The rule: per-row conditions belong in WHERE, per-group conditions belong in HAVING. A condition on status can be decided from a single order. A condition on COUNT(*) requires the group to exist before the comparison can run.
You practiced combining WHERE and HAVING in the same query. Per-row conditions go in WHERE; per-group conditions go in HAVING — that division of labour is the everyday shape of a grouped query with filters.