How to Get the Latest Row per Group in MySQL
To get the latest row for every customer, account, or other group, rank rows within each group by a date or timestamp in descending order. In MySQL 8.0 and later, ROW_NUMBER() makes it easy to select exactly one row per group. Add a unique tie-breaker so equal timestamps produce a repeatable result.
MySQL describes how ROW_NUMBER() and PARTITION BY work in its window function reference. See the MySQL ROW_NUMBER() function reference for the function itself. For a general top-N version of this pattern, see How to Get the Top N Rows per Group in MySQL.
Sample orders
The examples use order_date to decide which order is latest and order_id to break ties:
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATETIME NOT NULL,
total DECIMAL(10, 2) NOT NULL
);
INSERT INTO orders (order_id, customer_id, order_date, total)
VALUES
(101, 1, '2026-09-17 09:00:00', 100.00),
(102, 1, '2026-09-20 10:00:00', 45.00),
(103, 1, '2026-09-20 10:00:00', 80.00),
(201, 2, '2026-09-18 14:00:00', 25.00),
(301, 3, '2026-09-19 12:00:00', 50.00);
Customer 1 has two orders at the latest timestamp. The order_id column determines which one wins when the query needs exactly one row.
Return exactly one latest row per group
Use ROW_NUMBER() to number each customer’s orders from newest to oldest. The outer query can then keep the first row in each partition:
WITH ranked_orders AS (
SELECT
order_id,
customer_id,
order_date,
total,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY order_date DESC, order_id DESC
) AS row_num
FROM orders
)
SELECT order_id, customer_id, order_date, total
FROM ranked_orders
WHERE row_num = 1
ORDER BY customer_id;
+----------+-------------+---------------------+--------+
| order_id | customer_id | order_date | total |
+----------+-------------+---------------------+--------+
| 103 | 1 | 2026-09-20 10:00:00 | 80.00 |
| 201 | 2 | 2026-09-18 14:00:00 | 25.00 |
| 301 | 3 | 2026-09-19 12:00:00 | 50.00 |
+----------+-------------+---------------------+--------+PARTITION BY customer_id restarts numbering for each customer. Within a partition, the newest timestamp gets the lowest row number. The final ORDER BY sorts the returned rows; it is separate from the ordering inside OVER.
If you only want the latest paid order per customer, filter to paid orders inside the CTE before numbering them. Filtering after row_num = 1 would instead rank all orders first and could discard a customer whose latest order is unpaid.
Return every row tied for the latest timestamp
If you want all orders that share each customer’s maximum timestamp, use RANK() without the unique order_id tie-breaker:
WITH ranked_orders AS (
SELECT
order_id,
customer_id,
order_date,
total,
RANK() OVER (
PARTITION BY customer_id
ORDER BY order_date DESC
) AS date_rank
FROM orders
)
SELECT order_id, customer_id, order_date, total
FROM ranked_orders
WHERE date_rank = 1
ORDER BY customer_id, order_id;
For customer 1, this returns both orders 102 and 103 because they share the latest timestamp. ROW_NUMBER() assigns peers different numbers; RANK() assigns tied rows the same rank. See the MySQL window function descriptions for the distinction.
MySQL 5.7 and earlier: join to the maximum date
MySQL 5.7 does not have window functions. To return every row tied at the latest timestamp, group by the customer to find the maximum date, then join back to the original table:
SELECT o.order_id, o.customer_id, o.order_date, o.total
FROM orders AS o
JOIN (
SELECT customer_id, MAX(order_date) AS latest_order_date
FROM orders
GROUP BY customer_id
) AS latest
ON latest.customer_id = o.customer_id
AND latest.latest_order_date = o.order_date
ORDER BY o.customer_id, o.order_id;
This join returns both customer 1 orders in the sample because both have the maximum order_date. To select only one row per customer on MySQL 5.7, use a tie-breaker with NOT EXISTS:
SELECT o.order_id, o.customer_id, o.order_date, o.total
FROM orders AS o
WHERE NOT EXISTS (
SELECT 1
FROM orders AS newer
WHERE newer.customer_id = o.customer_id
AND (
newer.order_date > o.order_date
OR (newer.order_date = o.order_date AND newer.order_id > o.order_id)
)
)
ORDER BY o.customer_id;
Summary
- Use
ROW_NUMBER()withPARTITION BYto return exactly one latest row per group on MySQL 8.0 and later. - Include a unique sort key after the timestamp when ties must resolve consistently.
- Use
RANK()or aMAX()join when all rows tied at the latest date should be returned. - MySQL 5.7 can use a
MAX()join or a correlatedNOT EXISTScondition instead of a window function.