Brightlane's acquisition team is reviewing every customer's very first purchase to understand initial buying patterns.
Write a query to return one row per customer with at least one order, showing that customer's ID, the ID of their earliest order, when it was placed, and the order amount. Sort the final result by customer_id ascending.
Assumptions:
- A customer's earliest order is the order with the smallest
ordered_atfor thatcustomer_id. - Customers with no orders on record do not appear in the result.
- The final result is sorted by
customer_idascending.
Output:
- One row per customer with at least one order, with columns
customer_id,order_id,ordered_at, andtotal_amount. Sorted bycustomer_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 DISTINCT
ON (customer_id) customer_id,
id AS order_id,
ordered_at,
total_amount
FROM
orders
ORDER BY
customer_id,
ordered_at The shape
The earliest order per customer is the same DISTINCT ON pattern as latest-per-group, with the secondary sort pointing the other way. ORDER BY customer_id, ordered_at (ascending, the default) puts each customer's oldest order first, so PostgreSQL keeps the oldest row in each customer_id group.
Clause by clause
SELECT DISTINCT ON (customer_id) customer_id, id AS order_id, ordered_at, total_amountreturns the four columns the acquisition review needs. TheDISTINCT ON (customer_id)part declares the deduplication key: one row per distinctcustomer_id. Theid AS order_idalias names the order identifier column.FROM ordersreads the order records. Customers with no orders never enter this row source, so they cannot appear in the result.ORDER BY customer_id, ordered_atsorts the orders so each customer's earliest order sits first in their group. PostgreSQL keeps the first row for each newcustomer_idvalue, which is the oldest order for that customer. The leadingcustomer_idascending also gives the final result the customer-ordered shape the prompt asks for.
Why this and not ROW_NUMBER
The same first-order-per-customer pick is reachable with a window function:
SELECT customer_id, order_id, ordered_at, total_amount
FROM (
SELECT customer_id, id AS order_id, ordered_at, total_amount,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY ordered_at) AS rn
FROM orders
) ranked
WHERE rn = 1
ORDER BY customer_idBoth return the same rows. DISTINCT ON swaps from latest to earliest by flipping a single keyword — drop DESC from the secondary sort, the rest of the query is unchanged. That is the load-bearing detail: the direction of the secondary sort controls whether the kept row is the freshest or the oldest in each group.
You practiced DISTINCT ON with ascending secondary sort — same shape as latest-per-group, but the secondary sort points the other way so the earliest record wins.