SQL select only rows with max value on a column
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Selecting rows that contain the maximum value of a column — especially the maximum per group — is one of the most common SQL tasks. There are several approaches: subquery with MAX(), JOIN with an aggregated subquery, window functions (ROW_NUMBER, RANK), and correlated subqueries. The best approach depends on your database engine and whether you need ties included.
Simple Case: Global Maximum
Get all rows where a column equals the overall maximum:
This returns all rows with the maximum amount. If multiple rows share the max value, all are returned.
Per-Group Maximum: The Common Problem
The more common scenario is finding the row with the maximum value in each group. For example, finding the most recent order for each customer:
Method 1: Subquery with GROUP BY
This joins the table with a subquery that finds the max date per customer. If two orders share the same max date for a customer, both are returned.
Method 2: Window Function with ROW_NUMBER (Recommended)
ROW_NUMBER() assigns 1 to the most recent order per customer. This guarantees exactly one row per group, even with ties — the database picks one arbitrarily.
Method 3: Window Function with RANK (Include Ties)
RANK() assigns the same rank to tied values. If two orders have the same highest amount for a customer, both get rnk = 1.
| Function | Ties | Result |
ROW_NUMBER() | Breaks ties arbitrarily | Exactly one row per group |
RANK() | Same rank for ties | Multiple rows possible |
DENSE_RANK() | Same rank for ties, no gaps | Multiple rows possible |
Method 4: Correlated Subquery
This is readable but can be slow on large tables because the subquery runs once per row in the outer query.
Method 5: LEFT JOIN Anti-Pattern
Find rows where no other row in the same group has a higher value:
If no row o2 exists with a later date for the same customer, then o has the max date.
Max Value with Additional Columns
Get the maximum amount and the corresponding order details per customer:
Top N Per Group
Get the top 3 orders by amount for each customer:
Database-Specific Syntax
MySQL 8.0+
MySQL 5.7 (No Window Functions)
PostgreSQL (DISTINCT ON)
DISTINCT ON is PostgreSQL-specific and returns the first row per group based on the ORDER BY.
Performance Considerations
| Method | Performance | Ties | Portability |
| ROW_NUMBER | Fast with index | No ties | SQL standard |
| JOIN + MAX subquery | Fast with index | Includes ties | All databases |
| Correlated subquery | Slow on large tables | Includes ties | All databases |
| LEFT JOIN anti-pattern | Moderate | Includes ties | All databases |
| DISTINCT ON | Fast | No ties | PostgreSQL only |
Common Pitfalls
- Ambiguous max with GROUP BY:
SELECT customer_id, MAX(amount), order_date FROM orders GROUP BY customer_iddoes not return theorder_dateof the row with the max amount — it returns an arbitraryorder_date. Use window functions or a JOIN to get correct associated columns. - Performance on large tables: Correlated subqueries execute once per outer row and can be extremely slow. Prefer JOIN or window function approaches with appropriate indexes.
- Ties: Decide upfront whether you want one row per group (use
ROW_NUMBER) or all tied rows (useRANKorJOINwithMAX). - NULL values:
MAX()ignores NULLs. If the column can be NULL, rows with NULL are excluded from the maximum calculation. UseCOALESCEif NULLs should be treated as a specific value. - Index usage: Window functions benefit from composite indexes that match the
PARTITION BYandORDER BYcolumns.
Summary
- Use
ROW_NUMBER() OVER (PARTITION BY group ORDER BY col DESC)for exactly one row per group (most versatile) - Use
RANK()orJOINwithMAX()when tied maximum values should all be returned - Avoid correlated subqueries on large tables — they are slow
- PostgreSQL's
DISTINCT ONis the most concise syntax for this pattern - Always index the partition and order columns for window function queries

