Menu

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() with PARTITION BY to 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 a MAX() join when all rows tied at the latest date should be returned.
  • MySQL 5.7 can use a MAX() join or a correlated NOT EXISTS condition instead of a window function.