00:00

MySQL max() Function

Last updated: 4 min read

MAX() returns the largest value in a set: the longest run, the highest price, the most recent date. On its own it's simple. The interesting part is answering the follow-up question: which row had that maximum?

1. Basic MAX

Table: runs (8 rows)

run_idathletekmminutesrun_datecity
1Kabir10.0522026-03-01Pune
2Kabir21.11182026-03-08Pune
3Kabir15.0802026-03-15Lonavala
4Lata21.11052026-03-02Mumbai
5Lata42.22362026-03-09Mumbai
6Manav5.0312026-03-03Pune
7Manav10.0NULL2026-03-10Pune
8Nora42.22512026-03-16Goa
query.sql
SELECT MAX(km) AS longest_run, MAX(minutes) AS slowest_time, MAX(run_date) AS latest_run FROM runs;

Result: 1 row

longest_runslowest_timelatest_run
42.22512026-03-16

MAX ignores NULLs (Manav's missing time). For text it returns the value that sorts last in the collation:

query.sql
SELECT MAX(athlete) AS last_alphabetically, MAX(city) AS city FROM runs;

Result: 1 row

last_alphabeticallycity
NoraPune

2. MAX per Group

query.sql
SELECT athlete, MAX(km) AS best_km, MAX(run_date) AS last_run FROM runs GROUP BY athlete ORDER BY best_km DESC;

Result: 4 rows

athletebest_kmlast_run
Lata42.22026-03-09
Nora42.22026-03-16
Kabir21.12026-03-15
Manav10.02026-03-10
query.sql
SELECT city, MAX(km) AS longest FROM runs GROUP BY city HAVING MAX(km) >= 20;

Result: 3 rows

citylongest
Pune21.1
Mumbai42.2
Goa42.2

3. Getting the Whole Row with the Maximum

The row with the maximum per groupRuns grouped by athlete. MAX finds the best distance for each athlete: Kabir 21.1, Lata 42.2. Fetching the whole row with that distance also gives the date it happened.runsathletekmdateKabir10.0Mar 01Kabir21.1Mar 08Kabir15.0Mar 15Lata21.1Mar 02Lata42.2Mar 09MAX(km) per athlete+ fetch the matching rowbest runsathletekmdateKabir21.1Mar 08Lata42.2Mar 09
Figure 1: MAX gives the highest value; a little more SQL returns the full row it came from.

A common mistake is selecting other columns next to MAX and expecting them to come from the same row. With ONLY_FULL_GROUP_BY MySQL refuses:

query.sql
SELECT athlete, MAX(km), run_date FROM runs GROUP BY athlete;
Output
ERROR 1055 (42000): Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'fitness.runs.run_date' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

Method 1: correlated sub-query.

query.sql
SELECT athlete, km, run_date
FROM runs r
WHERE km = (SELECT MAX(km) FROM runs WHERE athlete = r.athlete)
ORDER BY athlete;

Result: 4 rows

athletekmrun_date
Kabir21.12026-03-08
Lata42.22026-03-09
Manav10.02026-03-10
Nora42.22026-03-16

Method 2: window function (MySQL 8+), which also lets you choose how to break ties:

row-number.sql
SELECT athlete, km, run_date
FROM (
  SELECT r.*, ROW_NUMBER() OVER (PARTITION BY athlete ORDER BY km DESC, run_date) AS rn
  FROM runs r
) ranked
WHERE rn = 1
ORDER BY athlete;

Result: 4 rows

athletekmrun_date
Kabir21.12026-03-08
Lata42.22026-03-09
Manav10.02026-03-10
Nora42.22026-03-16

For the single overall maximum, ORDER BY + LIMIT is simplest:

query.sql
SELECT athlete, km, run_date FROM runs ORDER BY km DESC, run_date LIMIT 1;

Result: 1 row

athletekmrun_date
Lata42.22026-03-09

4. Ties

Lata and Nora both ran 42.2 km. The sub-query method returns every tied row; LIMIT 1 and ROW_NUMBER pick one:

query.sql
SELECT athlete, km FROM runs WHERE km = (SELECT MAX(km) FROM runs);

Result: 2 rows

athletekm
Lata42.2
Nora42.2
Use RANK() instead of ROW_NUMBER() if you want all tied rows per group.

5. MAX of an Expression

query.sql
SELECT athlete, ROUND(MAX(km / minutes * 60), 2) AS best_speed_kmh FROM runs GROUP BY athlete ORDER BY best_speed_kmh DESC;

Result: 4 rows

athletebest_speed_kmh
Lata12.06
Kabir11.54
Nora10.09
Manav9.68

6. MAX vs GREATEST

MAX() compares values across rows; GREATEST() compares values across columns in one row:

query.sql
SELECT GREATEST(4, 19, 7) AS greatest, GREATEST(10, NULL) AS with_null;

Result: 1 row

greatestwith_null
19NULL
GREATEST returns NULL if any argument is NULL, while MAX skips NULLs.

7. MAX and Indexes

With an index on the column, MySQL reads the maximum straight from the end of the index (Select tables optimized away):

index.sql
CREATE INDEX idx_km ON runs (km);
EXPLAIN SELECT MAX(km) FROM runs;

Result: 1 row

typekeyExtra
NULLNULLSelect tables optimized away

8. Practice Exercises

Exercise 1: The most recent run in each city.
solution.sql
SELECT city, MAX(run_date) AS latest FROM runs GROUP BY city ORDER BY latest DESC;

Result: 4 rows

citylatest
Goa2026-03-16
Lonavala2026-03-15
Pune2026-03-10
Mumbai2026-03-09
Exercise 2: Each athlete's longest run, with its city, using ROW_NUMBER().
solution.sql
SELECT athlete, km, city FROM (
  SELECT athlete, km, city, ROW_NUMBER() OVER (PARTITION BY athlete ORDER BY km DESC) AS rn FROM runs
) t WHERE rn = 1;

Result: 4 rows

athletekmcity
Kabir21.1Pune
Lata42.2Mumbai
Manav10.0Pune
Nora42.2Goa

9. Summary

  • MAX(col) returns the largest non-NULL value: numbers, dates or text.
  • Use GROUP BY for a maximum per group.
  • To get the whole row, use a correlated sub-query, ROW_NUMBER()/RANK(), or ORDER BY … LIMIT 1.
  • GREATEST compares columns within a row; an index makes MAX instant.