MySQL last() Function
MySQL has no LAST() function; calling it is an error. What you usually want is the latest or most recent row: the last reading of each weather station, a customer's latest order, the newest log line. This tutorial shows the reliable ways to get it, including a trap in the LAST_VALUE() window function that catches almost everyone.
1. LAST() Doesn't Exist
SELECT LAST(temp_c) FROM readings;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(temp_c) FROM readings' at line 1
Table: readings (7 rows)
| id | station | read_on | temp_c | rain_mm |
|---|---|---|---|---|
| 1 | PUNE | 2026-03-01 | 18.0 | 0.0 |
| 2 | PUNE | 2026-03-02 | 21.0 | 0.0 |
| 3 | PUNE | 2026-03-03 | 19.0 | 2.5 |
| 4 | PUNE | 2026-03-04 | 24.0 | 0.0 |
| 5 | KOCHI | 2026-03-01 | 27.5 | 12.0 |
| 6 | KOCHI | 2026-03-02 | 28.1 | 8.4 |
| 7 | KOCHI | 2026-03-03 | 26.9 | 20.2 |
2. Last Row: ORDER BY … DESC LIMIT 1
SELECT station, read_on, temp_c FROM readings ORDER BY read_on DESC, id DESC LIMIT 1;
Most recent reading overall
| station | read_on | temp_c |
|---|---|---|
| PUNE | 2026-03-04 | 24.0 |
SELECT station, read_on, temp_c FROM readings WHERE station = 'PUNE' ORDER BY read_on DESC LIMIT 1;
Latest Pune reading
| station | read_on | temp_c |
|---|---|---|
| PUNE | 2026-03-04 | 24.0 |
3. Latest Value Only: MAX()
SELECT station, MAX(read_on) AS last_reading_date FROM readings GROUP BY station;
Result: 2 rows
| station | last_reading_date |
|---|---|
| PUNE | 2026-03-04 |
| KOCHI | 2026-03-03 |
4. Latest Row per Group
SELECT station, read_on, temp_c, rain_mm
FROM (
SELECT r.*, ROW_NUMBER() OVER (PARTITION BY station ORDER BY read_on DESC, id DESC) AS rn
FROM readings r
) t
WHERE rn = 1
ORDER BY station;
Result: 2 rows
| station | read_on | temp_c | rain_mm |
|---|---|---|---|
| KOCHI | 2026-03-03 | 26.9 | 20.2 |
| PUNE | 2026-03-04 | 24.0 | 0.0 |
An index on (station, read_on) makes this fast on big tables.
5. LAST_VALUE() and the Frame Trap
Look what happens with the default frame:
SELECT station, read_on, temp_c,
LAST_VALUE(temp_c) OVER (PARTITION BY station ORDER BY read_on) AS last_temp_wrong
FROM readings
ORDER BY station, read_on;
Result: 7 rows
| station | read_on | temp_c | last_temp_wrong |
|---|---|---|---|
| KOCHI | 2026-03-01 | 27.5 | 27.5 |
| KOCHI | 2026-03-02 | 28.1 | 28.1 |
| KOCHI | 2026-03-03 | 26.9 | 26.9 |
| PUNE | 2026-03-01 | 18.0 | 18.0 |
| PUNE | 2026-03-02 | 21.0 | 21.0 |
| PUNE | 2026-03-03 | 19.0 | 19.0 |
| PUNE | 2026-03-04 | 24.0 | 24.0 |
Each row just shows its own temperature, because the default frame stops at the current row. Make the frame cover the whole partition:
SELECT station, read_on, temp_c,
LAST_VALUE(temp_c) OVER (PARTITION BY station ORDER BY read_on
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_temp
FROM readings
ORDER BY station, read_on;
Result: 7 rows
| station | read_on | temp_c | last_temp |
|---|---|---|---|
| KOCHI | 2026-03-01 | 27.5 | 26.9 |
| KOCHI | 2026-03-02 | 28.1 | 26.9 |
| KOCHI | 2026-03-03 | 26.9 | 26.9 |
| PUNE | 2026-03-01 | 18.0 | 24.0 |
| PUNE | 2026-03-02 | 21.0 | 24.0 |
| PUNE | 2026-03-03 | 19.0 | 24.0 |
| PUNE | 2026-03-04 | 24.0 | 24.0 |
FIRST_VALUE(temp_c) OVER (PARTITION BY station ORDER BY read_on DESC) gives the latest value without any frame clause.6. Previous and Next Rows: LAG and LEAD
Often "last" means "the previous reading". LAG() looks back, LEAD() looks ahead:
SELECT station, read_on, temp_c,
LAG(temp_c) OVER (PARTITION BY station ORDER BY read_on) AS previous_temp,
temp_c - LAG(temp_c) OVER (PARTITION BY station ORDER BY read_on) AS change_c,
LEAD(read_on) OVER (PARTITION BY station ORDER BY read_on) AS next_reading
FROM readings
ORDER BY station, read_on;
Result: 7 rows
| station | read_on | temp_c | previous_temp | change_c | next_reading |
|---|---|---|---|---|---|
| KOCHI | 2026-03-01 | 27.5 | NULL | NULL | 2026-03-02 |
| KOCHI | 2026-03-02 | 28.1 | 27.5 | 0.6 | 2026-03-03 |
| KOCHI | 2026-03-03 | 26.9 | 28.1 | -1.2 | NULL |
| PUNE | 2026-03-01 | 18.0 | NULL | NULL | 2026-03-02 |
| PUNE | 2026-03-02 | 21.0 | 18.0 | 3.0 | 2026-03-03 |
| PUNE | 2026-03-03 | 19.0 | 21.0 | -2.0 | 2026-03-04 |
| PUNE | 2026-03-04 | 24.0 | 19.0 | 5.0 | NULL |
7. The Last Inserted ID
To get the ID your own session just inserted, use LAST_INSERT_ID(), never MAX(id), which can return another session's row:
INSERT INTO readings (station, read_on, temp_c, rain_mm) VALUES ('KOCHI', '2026-03-04', 29.0, 0);
SELECT LAST_INSERT_ID() AS my_new_id;
Result: 1 row
| my_new_id |
|---|
| 8 |
8. Which Technique to Use
| Need | Use |
|---|---|
| Latest row overall | ORDER BY col DESC LIMIT 1 |
| Latest value only | MAX(col) |
| Latest row in each group | ROW_NUMBER() OVER (PARTITION BY … ORDER BY … DESC) = 1 |
| Group's last value on every row | LAST_VALUE(…) OVER (… ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) |
| Previous / next row | LAG() / LEAD() |
| ID you just inserted | LAST_INSERT_ID() |
9. Summary
- MySQL has no LAST(); "last" needs an ORDER BY.
- Use ORDER BY … DESC LIMIT 1, MAX(), or ROW_NUMBER() per group.
- LAST_VALUE() needs a frame of UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
- LAG/LEAD reach the previous/next row; LAST_INSERT_ID() returns your session's new ID.