skip to content

How do you return each customer's most recent order row, with all of its columns?

level: juniorimportance: must knowfreq 70%

answer

  1. Top-N per group with N equal to one
  2. Partition by the key, order newest first
  3. Whole row survives, unlike an aggregate
  4. Filter rn = 1 one level out
  5. Add the primary key as a tiebreaker

basics

~20 s

Rank each customer's orders in a CTE with ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC), then select rows WHERE rn = 1 in an outer query. That keeps the whole row, not just the maximum date.

solid answer

~50 s

This is top-N-per-group with N = 1. Inside a CTE or derived table I write `ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn` alongside every column of `orders`, then the outer query keeps `WHERE rn = 1`. Because the window function preserves rows rather than collapsing them, the surviving row carries all of the order's columns — status, total, ship address — which a `MAX(order_date)` aggregate alone cannot give you. Two details matter. First, `ROW_NUMBER()` returns exactly one row per customer even when two orders share the same timestamp, whereas `RANK() = 1` would return both; if you want exactly one, keep `ROW_NUMBER()`. Second, add a unique tiebreaker such as `ORDER BY order_date DESC, order_id DESC` so the winner is reproducible rather than arbitrary. Customers with no orders do not appear at all.

code

sql · 9 lines
sql
WITH ranked AS (
    SELECT o.*,
           ROW_NUMBER() OVER (PARTITION BY o.customer_id
                              ORDER BY o.order_date DESC, o.order_id DESC) AS rn
    FROM orders o
)
SELECT customer_id, order_id, order_date, status, total_amount
FROM ranked
WHERE rn = 1;

go deeper

for a junior

You will be asked to write this one cold. Practise it until the shape is automatic: CTE with ROW_NUMBER partitioned by the key and ordered newest first, outer query filtering rn = 1.

for a middle

Explain why an aggregate cannot return the rest of the row, and what happens when two rows share the top timestamp under ROW_NUMBER versus RANK.

for a senior

Show that you make the result reproducible on purpose — a unique tiebreaker in the window ORDER BY, explicit NULL placement, and a stated answer for keys that have no rows at all.

for a principal

Be ready to argue about where 'current row per key' should live: repeated ad hoc in every query, or defined once as a shared view so that every consumer agrees on what 'latest' means.

## The requirement "The latest row per key" is the most frequently written query in this whole family: the newest order per customer, the current status per ticket, the last reading per sensor, the most recent price per product. It is top-N-per-group with N fixed at 1, and the output must be the entire row — not just the maximum timestamp, but the order id, amount and status that belong to it. ## The query ```sql WITH ranked AS ( SELECT o.*, ROW_NUMBER() OVER (PARTITION BY o.customer_id ORDER BY o.order_date DESC, o.order_id DESC) AS rn FROM orders o ) SELECT customer_id, order_id, order_date, status, total_amount FROM ranked WHERE rn = 1; ``` Read it in three beats. `PARTITION BY customer_id` restarts the numbering for every customer. `ORDER BY order_date DESC` puts the newest order first, so it receives number 1. The outer `WHERE rn = 1` keeps exactly that row. The filter has to sit in an outer level because a window function cannot appear in `WHERE` — at the time `WHERE` is evaluated the rank has not been computed yet. ## Why the aggregate version is not enough The instinctive first attempt is `SELECT customer_id, MAX(order_date) FROM orders GROUP BY customer_id`. That correctly finds *when* each customer last ordered, but it throws away the row: you cannot add `order_id` or `status` to the select list, because grouping has collapsed many rows into one and those columns have no single value. Getting the full row back from an aggregate requires joining the aggregate result back to `orders` on both `customer_id` and `order_date` — an extra join that also duplicates any customer with two orders at the identical timestamp. The window-function version keeps the row intact from the start, which is why it has become the default idiom. ## Ties and determinism Suppose a customer placed two orders recorded with the exact same `order_date`. With `ROW_NUMBER()` one of them gets 1 and the other gets 2, so the query still returns a single row per customer — but *which* of the two is unspecified, and a second run may pick the other one. That instability is invisible in small test data and surfaces later as a report that changes without any data changing. The fix is to make the ordering total by appending a column that is unique within the partition, typically the primary key: `ORDER BY order_date DESC, order_id DESC`. Now the newest order is defined precisely and the query is reproducible. If, instead, you genuinely want both tied orders returned, use `RANK() OVER (...) = 1`, which assigns 1 to every row sharing the top value. Choosing `RANK()` by accident is the classic cause of "my per-customer query returns more rows than I have customers". ## Timestamps, dates and the ordering column The pattern is only as good as the ordering column. If `order_date` is a date rather than a timestamp, every order placed on the same day ties, and the tiebreaker does all the work. If the column is nullable, engines differ on default NULL placement in `DESC` order, so an order with a NULL date can be picked as "most recent"; write `NULLS LAST` explicitly or filter the NULLs out in the inner query. ## What the query does not return Because the pattern reads `orders`, a customer who has never ordered produces no partition and therefore no output row. If the report must list every customer, start from `customers` and outer-join to the ranked set instead — the ranking logic is unchanged, only the driving table differs. ## Variations you should be able to write on the spot Changing `rn = 1` to `rn <= 5` gives the five most recent orders per customer with no other edit. Reversing to `ORDER BY order_date ASC` gives each customer's first order — useful for cohort and acquisition analysis. Partitioning by two columns (`PARTITION BY customer_id, product_id`) gives the latest order of each product per customer. The shape never changes; only the `OVER` clause and the outer predicate do.

  • What changes if you filter RANK() = 1 instead of ROW_NUMBER() = 1?
    `RANK()` gives every row sharing the top `order_date` the number 1, so a customer with two orders at the identical timestamp returns two rows and the result no longer has one row per customer. `ROW_NUMBER()` always returns exactly one. Use `RANK() = 1` only when returning all tied rows is the requirement.
  • Why add order_id to the window ORDER BY when order_date already sorts the rows?
    Because `order_date` may not be unique within a customer. When it ties, SQL specifies no rule for which row gets number 1, so the query can return a different order on each run. Appending the primary key makes the ordering total and the result reproducible.
  • How do you list every customer, including those who have never ordered?
    Drive the query from the customer table and outer-join to the ranked set: `FROM customers c LEFT JOIN ranked r ON r.customer_id = c.customer_id AND r.rn = 1`. Customers with no orders come back with NULL order columns. The ranking logic itself does not change.

saying these in an interview costs you the question

  • Uses MAX(order_date) and expects the other columns too
  • Adds LIMIT 1, returning a single row for the whole table
  • Filters rn = 1 in the same level as the window function
  • Omits a tiebreaker and calls the result deterministic
  • Expects customers with zero orders to appear in the output

context