🥇 Peeking Into the Window: FIRST_VALUE(), LAST_VALUE(), NTH_VALUE()


🚀 Peeking Behind the Rows: Why These Functions Matter

"Xorthax doesn’t just want totals—he’s nosy. He wants to know what happened first, what came last, and what the third-highest bid was. These functions let you peek at specific rows in the window frame without collapsing data."


🤔 What Are FIRST_VALUE(), LAST_VALUE(), and NTH_VALUE()?

These functions let you return a specific value from within the window frame for each row.

They’re great for comparisons, flags, or “difference from benchmark” logic.


🔧 Syntax Snapshot

FIRST_VALUE(column) OVER (...)
LAST_VALUE(column) OVER (...)
NTH_VALUE(column, n) OVER (...)

Typically used with PARTITION BY and ORDER BY, and very often customized with a frame clause to control what “first” and “last” mean.


🪟 What Is a Frame Clause?

A frame clause defines which rows are included when a window function runs for the current row. It controls the “visible slice” of the partition.

Examples:

This is critical for LAST_VALUE() and NTH_VALUE() — without it, they might return the current row’s value instead of the truly last or nth.


📋 See It in Action

🧾 Sample Data

CustomerID OrderDate Amount
C1 2023-01-01 100
C1 2023-01-10 200
C1 2023-01-20 300

🔎 Query Example

SELECT CustomerID, OrderDate, Amount,
       FIRST_VALUE(Amount) OVER (PARTITION BY CustomerID ORDER BY OrderDate) AS FirstAmt,
       LAST_VALUE(Amount) OVER (PARTITION BY CustomerID ORDER BY OrderDate ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS LastAmt,
       NTH_VALUE(Amount, 2) OVER (PARTITION BY CustomerID ORDER BY OrderDate) AS SecondAmt
FROM Orders;

📊 Result

OrderDate Amount FirstAmt LastAmt SecondAmt
2023-01-01 100 100 300 NULL
2023-01-10 200 100 300 200
2023-01-20 300 100 300 200

🔍 Explanation:


🧭 When Should You Use Each?


🧠 Keywords That Hint You're in the Right Place


⚠️ Common Pitfalls


🔄 CTEs and Difference Calculations

Compare each row to the first or last value in its partition.

WITH Labeled AS (
  SELECT *,
         FIRST_VALUE(Amount) OVER (PARTITION BY CustomerID ORDER BY OrderDate) AS FirstAmt
  FROM Orders
)
SELECT CustomerID, OrderDate, Amount, FirstAmt,
       Amount - FirstAmt AS DiffFromFirst
FROM Labeled;

🔍 Explanation: This query uses a CTE to assign the first value in each partition to every row. Then, in the outer query, it subtracts that first amount from the current row’s amount to calculate the difference.

The CTE is necessary because you can't reference a window function directly in the same SELECT clause where you apply a transformation like subtraction.


➕ Combine with Aggregates

Use these value-based functions to set context before aggregating:

WITH Anchored AS (
  SELECT CustomerID, OrderDate, Amount,
         FIRST_VALUE(OrderDate) OVER (PARTITION BY CustomerID ORDER BY OrderDate) AS StartDate
  FROM Orders
)
SELECT CustomerID,
       MIN(OrderDate) AS FirstOrder,
       MAX(OrderDate) AS LastOrder,
       COUNT(*) AS TotalOrders,
       MAX(OrderDate) - MIN(OrderDate) AS Duration
FROM Anchored
GROUP BY CustomerID;

🔍 Explanation: This query uses FIRST_VALUE() to anchor the starting date of a customer’s activity. Then it applies regular aggregate functions like MIN(), MAX(), and COUNT() to summarize the order history by customer. It shows how window functions can prepare contextual data before aggregation.


🧰 Debugging Tips



🔗 Related Topics

*(See also: Frame Clauses, Aggregates, Lag/Lead)*