Home Functions LAST_VALUE()
SQL Window Function

LAST_VALUE()

LAST_VALUE() is a window function that returns the value from the last row of the current window frame. The default frame stops at the current row, so you almost always need an explicit full frame to get the real last value.

MySQLPostgreSQLSQL ServerSQLite
Returns: A single value taken from the last row of the current window frame. Its data type matches the column or expression passed in.

Syntax

LAST_VALUE(expression) OVER (ORDER BY ...)LAST_VALUE(expression) OVER (PARTITION BY ... ORDER BY ...)LAST_VALUE(expression) OVER (PARTITION BY ... ORDER BY ... ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
ParameterTypeRequiredDescription
expression column or expression yes The value to read from the last row of the frame. Usually a single column.
PARTITION BY clause no Splits the rows into independent groups; the last value is computed separately per partition.
ORDER BY clause yes Defines the order of rows inside each partition. Without it there is no meaningful "last" row.
frame clause no The ROWS or RANGE clause that bounds the frame. Almost always required in practice: use ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING to reach the true last value.

How it works

LAST_VALUE() is a window function: it returns the value of an expression evaluated on the last row of the current window frame, while keeping every input row instead of collapsing them like an aggregate. It is the mirror image of FIRST_VALUE(), and both belong to the same family as NTH_VALUE().

Here is the single most important thing to understand, and the reason most LAST_VALUE() queries look broken. When you write an ORDER BY inside OVER() but do not specify a frame, SQL applies a default frame of RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. That frame ends at the current row, so LAST_VALUE() returns the value of the current row on every line, not the last row of the partition. It looks like the function does nothing useful.

To get the true last value of the whole partition you must widen the frame yourself with ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING. This tells the engine to look ahead to the final row of the partition. See the window functions guide for how frames work in general, and browse the full SQL functions library for related tools.

Examples

The default-frame trap: WRONG vs RIGHT side by side

-- WRONG: no frame, so the default RANGE ... CURRENT ROW
-- makes LAST_VALUE return the CURRENT row's amount
SELECT id, amount,
       LAST_VALUE(amount) OVER (ORDER BY id) AS wrong_last
FROM orders;

-- RIGHT: widen the frame to the whole partition
SELECT id, amount,
       LAST_VALUE(amount) OVER (
         ORDER BY id
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       ) AS right_last
FROM orders;
Result
id | amount | wrong_last | right_last
---+--------+------------+-----------
 1 |    100 |        100 |        450
 2 |    250 |        250 |        450
 3 |    450 |        450 |        450

Latest status per entity

-- Show the most recent status next to every event row
SELECT ticket_id, changed_at, status,
       LAST_VALUE(status) OVER (
         PARTITION BY ticket_id
         ORDER BY changed_at
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       ) AS current_status
FROM ticket_history;
Result
ticket_id | changed_at | status  | current_status
----------+------------+---------+---------------
       10 | 2026-01-02 | open    | closed
       10 | 2026-01-05 | pending | closed
       10 | 2026-01-09 | closed  | closed

Most recent order amount on every row

-- Every customer row carries their latest order amount
SELECT customer_id, order_date, amount,
       LAST_VALUE(amount) OVER (
         PARTITION BY customer_id
         ORDER BY order_date
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       ) AS latest_amount
FROM orders;
Result
customer_id | order_date | amount | latest_amount
------------+------------+--------+--------------
          7 | 2026-02-01 |    120 |           90
          7 | 2026-03-14 |    300 |           90
          7 | 2026-04-20 |     90 |           90

FIRST_VALUE with reversed ORDER BY: the simpler alternative

-- Same result as LAST_VALUE with a full frame, but no
-- frame clause needed: just reverse the sort and take
-- the first row. Often easier to read.
SELECT customer_id, order_date, amount,
       FIRST_VALUE(amount) OVER (
         PARTITION BY customer_id
         ORDER BY order_date DESC
       ) AS latest_amount
FROM orders;
Result
customer_id | order_date | amount | latest_amount
------------+------------+--------+--------------
          7 | 2026-02-01 |    120 |           90
          7 | 2026-03-14 |    300 |           90
          7 | 2026-04-20 |     90 |           90

Common mistakes

Wrong
-- The #1 LAST_VALUE bug: no frame clause.
-- The default frame ends at the CURRENT row, so this
-- returns the current row's value, not the last one.
SELECT id, amount,
       LAST_VALUE(amount) OVER (ORDER BY id) AS last_amount
FROM orders;
Right
-- Add an explicit full frame so the window reaches
-- the final row of the partition.
SELECT id, amount,
       LAST_VALUE(amount) OVER (
         ORDER BY id
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       ) AS last_amount
FROM orders;

This is the single most common LAST_VALUE() mistake. An ORDER BY inside OVER() with no frame defaults to RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which stops at the current row. Always add ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING to get the true last value.

Wrong
-- No ORDER BY, so there is no defined "last" row.
-- The result is arbitrary and engine dependent.
SELECT id, amount,
       LAST_VALUE(amount) OVER (PARTITION BY customer_id) AS last_amount
FROM orders;
Right
-- ORDER BY gives the rows a meaningful sequence,
-- so "last" actually means something.
SELECT id, amount,
       LAST_VALUE(amount) OVER (
         PARTITION BY customer_id
         ORDER BY order_date
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       ) AS last_amount
FROM orders;

Without ORDER BY the window has no ordering, so "the last row" is undefined and the result you get is not reliable. LAST_VALUE() only makes sense with an explicit ORDER BY.

Wrong
-- Fighting with the frame clause when all you want
-- is the most recent value per group.
SELECT customer_id, amount,
       LAST_VALUE(amount) OVER (
         PARTITION BY customer_id
         ORDER BY order_date
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       ) AS latest
FROM orders;
Right
-- Reverse the sort and use FIRST_VALUE: no frame
-- clause, and the intent is clearer.
SELECT customer_id, amount,
       FIRST_VALUE(amount) OVER (
         PARTITION BY customer_id
         ORDER BY order_date DESC
       ) AS latest
FROM orders;

When you only need the last value, FIRST_VALUE() with a reversed ORDER BY is often simpler: it needs no frame clause and reads more clearly. Reach for LAST_VALUE() when the natural sort must stay ascending.

Performance

LAST_VALUE() requires the engine to build and sort each partition, so an index that matches your PARTITION BY and ORDER BY columns lets the optimiser avoid an extra sort step. Without a supporting index, large partitions mean a full sort in memory or spilling to disk.

Using the full frame ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING is cheap once the partition is sorted, because the last row is already known. If you need the same last value on many rows, computing it once with a window function is usually faster than a correlated subquery that re-scans the table for every row. See the window functions guide for more.

Interview questions

Why does LAST_VALUE() often return the current row instead of the last row?

Because an ORDER BY inside OVER() with no frame defaults to RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. That frame ends at the current row, so LAST_VALUE() reads the current row. You must widen the frame with ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.

How do you make LAST_VALUE() return the true last value of a partition?

Add an explicit frame of ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING so the window covers every row in the partition, from the first to the last.

SELECT customer_id, amount,
       LAST_VALUE(amount) OVER (
         PARTITION BY customer_id
         ORDER BY order_date
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
       ) AS latest
FROM orders;

What is the difference between FIRST_VALUE() and LAST_VALUE()?

FIRST_VALUE() returns the value from the first row of the frame and works correctly with the default frame, because that frame already starts at the first row. LAST_VALUE() returns the value from the last row of the frame, but the default frame ends at the current row, so it usually needs an explicit full frame.

When would you use FIRST_VALUE() instead of LAST_VALUE()?

When you only need the most recent value, reversing the ORDER BY and using FIRST_VALUE() avoids the frame clause entirely and is easier to read. Use LAST_VALUE() when the ascending sort must be preserved for other columns in the same query.

Does LAST_VALUE() require an ORDER BY?

In practice yes. Without an ORDER BY inside OVER() the rows have no defined order, so "the last row" is undefined and the result is arbitrary. Always provide an ORDER BY so that "last" has a clear meaning.

Master SQL, one function at a time

Browse the full SQL functions library, or learn the fundamentals with our free, structured courses.