Brightlane's CRM team is building a customer engagement dashboard and needs order volume and revenue figures for every customer in a single result set.
Write a query to return every customer's ID, name, total number of orders placed, and total amount spent across every order.
Assumptions:
- A customer's order count is the number of orders linked to that
customer_id. The total spend is the combinedtotal_amountacross those orders. - Every customer must appear in the result. Customers with no orders on record should show an order count of
0and a missing total spend.
Output:
- One row per customer, with columns
id,name,order_count, andtotal_spent.
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
c.id,
c.name,
stats.order_count,
stats.total_spent
FROM
customers c
CROSS JOIN LATERAL (
SELECT
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent
FROM
orders o
WHERE
o.customer_id = c.id
) stats The shape
The lateral subquery sees c.id from the outer customer row and reduces that customer's orders to one row of stats. CROSS JOIN LATERAL makes that per-row execution legal; the aggregate without GROUP BY collapses each customer's orders into a single order_count plus total_spent, so every customer in the engagement dashboard contributes exactly one output row.
Clause by clause
SELECT c.id, c.name, stats.order_count, stats.total_spentreturns the four columns the dashboard asks for, pulling the first two from the outer customer and the last two from the lateral result.FROM customers cis the driving table. Every row here will be paired with one row from the lateral, so every customer is preserved in the result.CROSS JOIN LATERAL ( SELECT COUNT(*) AS order_count, SUM(total_amount) AS total_spent FROM orders o WHERE o.customer_id = c.id ) statsruns once per customer. The aggregate has noGROUP BY, so it always returns exactly one row. For a customer with no orders,COUNT(*)returns0andSUMreturns NULL, which is the missing total spend the prompt asks for. Because the lateral always returns one row,CROSS JOINkeeps every customer.
The trap
CROSS JOIN LATERAL drops outer rows whose lateral returns zero rows, which would lose customers with no orders. The aggregate without GROUP BY is what saves this query. COUNT(*) over an empty input is 0 and a one-row result, not zero rows, so the lateral always returns one row per customer. Swap in a non-aggregated lateral (SELECT id FROM orders WHERE o.customer_id = c.id) and the no-order customers vanish from the dashboard.
You practiced CROSS JOIN LATERAL (SELECT aggregate ...) where the inner aggregate has no GROUP BY — every outer record pairs with exactly one row from the lateral, even when zero child records match (COUNT returns 0, SUM returns missing).