MySQL
SQL JOIN
database query
recent row
SQL optimization

MySQL JOIN the most recent row only?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Joining only the most recent related row is a classic SQL pattern sometimes called "greatest-n-per-group." The correct query depends on your MySQL version and on how you define "most recent" when two rows share the same timestamp. Once that rule is explicit, the join becomes much easier to write correctly.

The Problem Setup

Suppose you have customers and orders, and you want each customer joined to only their latest order.

sql
1CREATE TABLE customers (
2  id INT PRIMARY KEY,
3  name VARCHAR(50) NOT NULL
4);
5
6CREATE TABLE orders (
7  id INT PRIMARY KEY,
8  customer_id INT NOT NULL,
9  created_at DATETIME NOT NULL,
10  total DECIMAL(10, 2) NOT NULL
11);

The wrong approach is a plain join, because that returns every matching order, not just the newest one.

MySQL 8.0+: Use ROW_NUMBER()

If you have MySQL 8.0 or later, window functions make this much cleaner.

sql
1WITH ranked_orders AS (
2  SELECT
3    o.*,
4    ROW_NUMBER() OVER (
5      PARTITION BY o.customer_id
6      ORDER BY o.created_at DESC, o.id DESC
7    ) AS rn
8  FROM orders AS o
9)
10SELECT
11  c.id,
12  c.name,
13  ro.id AS order_id,
14  ro.created_at,
15  ro.total
16FROM customers AS c
17LEFT JOIN ranked_orders AS ro
18  ON ro.customer_id = c.id
19 AND ro.rn = 1;

This is usually the clearest answer. The ranking step says exactly how "most recent" is decided.

Why The Extra id DESC Helps

If two orders have the same created_at, ordering by timestamp alone leaves a tie. Adding id DESC gives the query a deterministic winner.

Without that tie-breaker, you can get inconsistent results when several rows share the same latest timestamp.

Older MySQL: Join Against A Max-Date Subquery

If window functions are not available, use a derived table that finds the maximum timestamp per parent row.

sql
1SELECT
2  c.id,
3  c.name,
4  o.id AS order_id,
5  o.created_at,
6  o.total
7FROM customers AS c
8LEFT JOIN (
9  SELECT customer_id, MAX(created_at) AS max_created_at
10  FROM orders
11  GROUP BY customer_id
12) AS latest
13  ON latest.customer_id = c.id
14LEFT JOIN orders AS o
15  ON o.customer_id = latest.customer_id
16 AND o.created_at = latest.max_created_at;

This works, but it can still return multiple rows per customer if two orders share the same latest timestamp. That is why deterministic tie-breaking matters.

Older MySQL With Tie-Breaking

If ties matter and you do not have window functions, you may need a second layer of aggregation or a correlated subquery.

sql
1SELECT
2  c.id,
3  c.name,
4  o.id AS order_id,
5  o.created_at,
6  o.total
7FROM customers AS c
8LEFT JOIN orders AS o
9  ON o.id = (
10    SELECT o2.id
11    FROM orders AS o2
12    WHERE o2.customer_id = c.id
13    ORDER BY o2.created_at DESC, o2.id DESC
14    LIMIT 1
15  );

This is often easier to read than stitching together several derived tables, and with the right index it can perform well.

Indexing Matters

Whichever form you choose, this index is usually important:

sql
CREATE INDEX idx_orders_customer_created
ON orders (customer_id, created_at, id);

Without a supporting index, MySQL may scan far more rows than necessary for each customer.

Decide Whether You Need LEFT JOIN Or INNER JOIN

Use LEFT JOIN if customers without orders should still appear with NULL order columns. Use INNER JOIN if only customers with at least one order should be returned.

That is a business-rule decision, not just a query-style preference.

Common Pitfalls

  • Writing a normal join and then being surprised that all related rows appear.
  • Using MAX(created_at) without a tie-breaker and getting duplicate "latest" rows.
  • Forgetting that MySQL 8.0 window functions simplify this pattern substantially.
  • Using INNER JOIN when rows with no related record should still be listed.
  • Ignoring indexes and then blaming the SQL pattern for poor performance.

Summary

  • Joining only the latest related row is a greatest-n-per-group problem.
  • In MySQL 8.0+, ROW_NUMBER() is usually the clearest solution.
  • In older MySQL, use a max-date subquery or a correlated subquery.
  • Add a tie-breaker such as id DESC when timestamps can be equal.
  • Index by parent key and recency columns to keep the query efficient.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.