HR wants every employee's row to also show their department's top earner's name, for quick comparison. FIRST_VALUE() does that correctly on the first try. Asking for the department's lowest earner the same way, with LAST_VALUE(), is where almost everyone gets a confusing, wrong-looking result the first time.
Learning Objectives
- Define FIRST_VALUE() and LAST_VALUE(), and what "first" and "last" actually mean, relative to the frame, not the partition.
- Reproduce, and fix, LAST_VALUE()'s classic default-frame mistake.
- Write a window frame explicitly, using ROWS BETWEEN.
Ordered Partition
salary DESC, within each department
Window Frame
Which rows count as 'the window', not just the partition
FIRST_VALUE / LAST_VALUE
Reads the frame's first or last row
Result
Correct only if the frame actually covers what's expected
What FIRST_VALUE() and LAST_VALUE() Actually Return
Both return a value from a specific row relative to the current row's window frame, the actual range of rows the function considers, not necessarily the entire partition. FIRST_VALUE() returns the frame's first row's value, LAST_VALUE() returns the frame's last row's value. The distinction between "frame" and "partition" is exactly where LAST_VALUE() goes wrong by default.
FIRST_VALUE() Works as Expected
This correctly shows the department's top earner's name on every row. It works because the default frame, when ORDER BY is present, runs from the partition's start up through the current row, and the current row is always at or after the frame's start, so FIRST_VALUE() always reaches that true first row correctly.
SELECT first_name, department_id, salary,
FIRST_VALUE(first_name) OVER (PARTITION BY department_id ORDER BY salary DESC) AS top_earner
FROM employees;LAST_VALUE() Doesn't, By Default
This does not return the department's lowest earner. The default frame (UNBOUNDED PRECEDING to CURRENT ROW, whenever ORDER BY is present) means the frame's "last row" is always the current row itself, so lowest_earner just echoes each row's own first_name right back, not the actual lowest-paid person in the department.
SELECT first_name, department_id, salary,
LAST_VALUE(first_name) OVER (PARTITION BY department_id ORDER BY salary DESC) AS lowest_earner
FROM employees;The Fix: an Explicit Frame
SELECT first_name, department_id, salary,
LAST_VALUE(first_name) OVER (
PARTITION BY department_id ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS lowest_earner
FROM employees;ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING explicitly extends the frame to cover the entire partition, every row before and after the current one, not just up to the current row. With that frame, LAST_VALUE() finally returns the true lowest earner on every row, correctly.
Try It Yourself
Fix this query so it correctly shows the most recently hired employee's name in every row of each department, on PostgreSQL: SELECT first_name, department_id, hire_date, LAST_VALUE(first_name) OVER (PARTITION BY department_id ORDER BY hire_date) AS most_recent_hire FROM employees;
SELECT first_name, department_id, hire_date,
LAST_VALUE(first_name) OVER (
PARTITION BY department_id ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS most_recent_hire
FROM employees;Frequently Asked Questions
Why doesn't FIRST_VALUE() have this same problem?
Because the default frame always starts at the partition's actual first row (UNBOUNDED PRECEDING) and ends at the current row, the frame's start never changes regardless of which row is current. The frame's end is what moves (it's always the current row), and that's specifically what LAST_VALUE() reads, which is why only LAST_VALUE() is affected.
Does this default-frame behavior apply when there's no ORDER BY at all?
No, with no ORDER BY, the default frame covers the entire partition from the start, so LAST_VALUE() actually does return the partition's true last row in that case. The gotcha specifically shows up once an ORDER BY is added, which is also exactly when ranking and sequencing usually become relevant in the first place.
Common Beginner Mistakes
Trusting LAST_VALUE() with ORDER BY and no explicit frame
This is the single most common window function mistake across all six databases in this course, the default frame quietly makes LAST_VALUE() return the current row's own value instead of the partition's actual last row. Always pair LAST_VALUE() with an explicit ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING whenever ORDER BY is present.
Assuming "first" and "last" refer to the whole partition
They refer to the current row's window frame specifically, which defaults to something narrower than the full partition once ORDER BY is involved. The frame, not the partition, is what FIRST_VALUE() and LAST_VALUE() actually read from.
Best Practices
- Always write an explicit ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING frame alongside LAST_VALUE() whenever ORDER BY is present.
- Test FIRST_VALUE()/LAST_VALUE() results against a row you can verify by eye before trusting them in a larger query.
- When in doubt about frame behavior, an explicit frame is always safer than relying on the default.
Interview Questions
Why does LAST_VALUE() often return the wrong result, and how is it fixed?
Whenever ORDER BY is present inside OVER() and no frame is explicitly given, the default frame runs from the partition's start to the current row, UNBOUNDED PRECEDING AND CURRENT ROW, which means the frame's own "last row" is always the current row itself. LAST_VALUE() then just returns the current row's own value, not the partition's true last row. Adding ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING explicitly extends the frame across the whole partition, fixing it.
Why doesn't FIRST_VALUE() need the same fix as LAST_VALUE()?
The default frame's start is always UNBOUNDED PRECEDING, the true first row of the partition, regardless of which row is currently being evaluated, so FIRST_VALUE() always has access to the real first row by default. It's the frame's end, which LAST_VALUE() depends on, that defaults to the current row instead of the partition's true end.
Summary
FIRST_VALUE() and LAST_VALUE() both read from the window frame, not necessarily the whole partition, a distinction that only matters once ORDER BY is involved. FIRST_VALUE() works correctly by default because the frame's start is always the true first row. LAST_VALUE() does not, its default frame ends at the current row, not the partition's true last row, requiring an explicit ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING to fix.
What's Next?
SQL Running Totals and Moving Averages is next: SUM() and AVG() as window functions, where, unlike LAST_VALUE(), the default frame is actually exactly what's needed.
Ready to Practice SQL?
Write and run real SQL queries online, right in your browser, no install or signup required. Pick a playground based on which database you want to practice on.
Both open a third-party SQL playground in a new tab. External site terms and privacy policy apply.
Or try 50 SQL practice questions →