Brightlane's CRM team needs a list of active buyers — customers who have at least one order on record.
Write a query to return the customer ID for every customer who appears in the orders table.
Assumptions:
- The
customerstable contains every customer Brightlane has on file. - The
orderstable contains every order;customer_ididentifies the buyer. - A customer with multiple orders appears once in the result.
Output:
- One row per active buyer, with a single column
customer_id.
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
id AS customer_id
FROM
customers
INTERSECT
SELECT
customer_id
FROM
orders The shape
INTERSECT keeps only the customer IDs that appear on both sides — every active buyer is on file in customers and has at least one row in orders, and the intersection returns exactly that overlap as one row per buyer. The CRM team gets the active-buyer headcount directly.
Clause by clause
SELECT id AS customer_id FROM customersis the left input. The alias renamesidtocustomer_idso both sides project a column with the same role and the same name. The output column comes from the left query's projection — that's why the alias goes here and not on the right.INTERSECTis the set operator. PostgreSQL evaluates both queries, deduplicates each side, then returns the values present in both. The deduplication is what makes the result one row per active buyer rather than one row per (customer, order) pair: a buyer with 12 orders shows up 12 times on the right side, collapses to once before the intersection runs, and contributes a single row to the output.SELECT customer_id FROM ordersis the right input. Thecustomer_idvalues here may repeat freely; the intersection doesn't care how many times a buyer appears, only whether they appear.
Why this and not EXCEPT
The complement question — customers with no orders — is exactly what swapping the operator answers. customers EXCEPT orders returns customer IDs in customers not in orders: the never-purchased list. The same two queries with the same projection answer two opposite questions depending on which set operator sits between them.
Which operator the CRM team needs depends on the framing: "active buyers" is the intersection, "dormant accounts" is the left-side EXCEPT. The shapes are symmetric in the SQL and opposite in the business meaning. Reading the operator is how the query's intent stays legible.
You practiced INTERSECT from a different angle: confirming presence on both sides. The same answer comes out of SELECT DISTINCT customer_id FROM orders WHERE customer_id IN (SELECT id FROM customers) — set operations sometimes read more cleanly than the equivalent JOIN/IN form.