Why does LAST_VALUE(price) OVER (ORDER BY ts) return the current row's price?
answer
- Value functions read the frame, not the partition
- An omitted frame is not "no frame"
- ORDER BY silently supplies a default frame
- That default ends at CURRENT ROW
- FIRST_VALUE never shows the bug
basics
~20 sBecause an OVER clause that has ORDER BY but no explicit frame defaults to RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, so the window ends at the current row. Add ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
solid answer
~40 s`LAST_VALUE` is frame-sensitive: it returns the value from the last row of the *frame*, not of the partition. When `OVER` contains an `ORDER BY` and no frame clause, the default frame is `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`, which stops at the current row — so the "last" row in scope is the current one (or the last of its peer group, since `RANGE` includes tied rows). Two fixes: widen the frame with `ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING`, or flip the problem and use `FIRST_VALUE(price) OVER (ORDER BY ts DESC)`. `FIRST_VALUE` does not show the bug because the default frame always starts at `UNBOUNDED PRECEDING`. And note that `LAG`/`LEAD` are unaffected by frames at all — only the value functions `FIRST_VALUE`, `LAST_VALUE` and `NTH_VALUE` are.
go deeper
Recognise the symptom: a LAST_VALUE column that just repeats the current row. Remember the fix phrase ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
Explain the default frame precisely — RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW when ORDER BY is present, whole partition when it is absent — and why FIRST_VALUE is immune.
Demonstrate review instinct: flag any LAST_VALUE or NTH_VALUE with an ordered, unframed window, and know that ties make the default RANGE frame reach through the whole peer group.
Own the convention: decide whether the team writes frames explicitly on every ordered window, and be able to justify that a silent default which changes results is worth a lint rule rather than a comment.
## The symptom A developer wants each row of a price history to carry the product's most recent price and writes: ```sql SELECT product_id, ts, price, LAST_VALUE(price) OVER (PARTITION BY product_id ORDER BY ts) AS latest_price FROM price_history; ``` Every row comes back with `latest_price` equal to its own `price`. Nothing errors; the column is simply a copy. This is the single most-asked trap about value functions. ## Why it happens Window functions that read a *position* inside the window — `FIRST_VALUE`, `LAST_VALUE`, `NTH_VALUE`, and all aggregates used with `OVER` — operate on the **frame**, a sub-range of the partition computed per row. The frame is defined by the frame clause, and when you omit it the standard supplies one: - `OVER (ORDER BY ...)` with no frame clause → `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`. - `OVER ()` or `OVER (PARTITION BY ...)` with **no** `ORDER BY` → the frame is the entire partition. So as soon as you add `ORDER BY` — which you must, for "last" to mean anything — the frame silently becomes everything from the start of the partition up to the current row. The last row of that frame is the current row. `LAST_VALUE` faithfully returns it. There is a second-order wrinkle: the default frame uses `RANGE`, not `ROWS`. `RANGE ... CURRENT ROW` extends to include all *peers* — rows whose `ORDER BY` values are equal to the current row's. So with tied timestamps, `LAST_VALUE` returns the value from the last row of the tie group, which can differ from the current row and looks even more mysterious. ## The fixes **Widen the frame explicitly.** This is the direct fix and it says what you mean: ```sql LAST_VALUE(price) OVER ( PARTITION BY product_id ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS latest_price ``` **Reverse the ordering and take the first value.** `FIRST_VALUE` is immune, because the default frame always begins at `UNBOUNDED PRECEDING` — the first row of the partition is always inside it: ```sql FIRST_VALUE(price) OVER (PARTITION BY product_id ORDER BY ts DESC) AS latest_price ``` Many practitioners prefer this form precisely because it cannot be broken by forgetting a frame. Watch the consequences of `DESC`, though: it also changes where NULLs sort, and it changes the meaning of any other function sharing that window. ## What it is not `LAST_VALUE(price)` is not `MAX(price)`. "The most recent price" and "the highest price" are different questions; the ordering column decides which row is last, and the argument decides which value comes back from it. Using `MAX` because it "seems to work" on a monotonic column is a bug waiting for the first price cut. It is also not the same family as `LAG`/`LEAD`. Offset functions address rows by position in the partition and are not governed by a frame clause at all — the standard does not permit a frame specification with them. So in one query, a `LAG` and a `LAST_VALUE` sharing the same `ORDER BY` respond differently to an added frame clause. Knowing which functions are frame-sensitive is the durable takeaway. ## NTH_VALUE inherits the same rule `NTH_VALUE(price, 3)` returns the value from the third row **of the frame**, so under the default frame it is NULL until the frame has grown to three rows, then it becomes stable. If you want the third row of the whole partition, widen the frame the same way. The standard also defines `FROM FIRST` / `FROM LAST` for `NTH_VALUE`, but engine support varies — check before depending on it. ## How to spot it in review A reliable heuristic: any `LAST_VALUE` or `NTH_VALUE` whose `OVER` clause contains `ORDER BY` but no frame clause is almost certainly wrong. Either the author wants the partition's end, in which case the frame is missing, or they want a running endpoint, in which case they should say so explicitly. Reviewers who know only this one rule catch most occurrences.
- Why does FIRST_VALUE never suffer from this problem?Because both default frames begin at `UNBOUNDED PRECEDING`. Whether the frame ends at the current row or spans the whole partition, its first row is the partition's first row, so `FIRST_VALUE` returns the same thing either way. Only the frame's *end* varies with the default, which is exactly what `LAST_VALUE` depends on.
- What does LAST_VALUE return under the default frame when several rows share the same ORDER BY value?The default frame is `RANGE`-based, so `CURRENT ROW` extends through the whole peer group of tied rows. `LAST_VALUE` returns the value from the last peer, which may not be the current row. Switching the frame to `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` would pin it to the current row — but if you wanted the partition's end, widen the frame to `UNBOUNDED FOLLOWING` instead.
- Does adding a frame clause change what LAG returns in the same query?No. `LAG` and `LEAD` address rows by position within the partition and are not governed by a frame; the standard does not allow a frame specification with them. Only `FIRST_VALUE`, `LAST_VALUE`, `NTH_VALUE` and aggregates used as window functions respond to the frame.
The default frame is a spotlight that lights only the track behind you. Asking it for the last thing it illuminates gets you your own feet, not the finish line.
saying these in an interview costs you the question
- Says LAST_VALUE is broken or buggy in the engine
- Believes omitting the frame means the whole partition
- Substitutes MAX for the most recent value
- Thinks the same trap affects LAG and LEAD
- Adds DISTINCT or a subquery instead of fixing the frame