Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

SQL (PostgreSQL, MySQL, SQLite)  Module 8 – Window functions

LAG, LEAD, FIRST_VALUE and NTH_VALUE

Compare each row with the one before or after it using LAG and LEAD, and read the first or nth row of a window with FIRST_VALUE and NTH_VALUE.

  • Intermediate
  • 30 minutes
  • Examples run with sql.js 1.14.2
  • By MySmartCoPilot
Free intro, Pro: 7 of 30 minutes free
Free: Looking at the row before and the row after. With a pass: the other 9 sections and 3 interview answers, about 23 minutes.

What you will learn

  • Compare a row with the previous or next rows of its window using LAG and LEAD
  • Retrieve the first, last and nth value of a window frame
  • Avoid the LAST_VALUE default-frame trap
  • Compute gaps in days and month-over-month growth that stay right when rows are missing

Before you start

What you’ll learn · Before you start

What you will learn

  • Compare a row with the previous or next rows of its window using LAG and LEAD
  • Retrieve the first, last and nth value of a window frame
  • Avoid the LAST_VALUE default-frame trap
  • Compute gaps in days and month-over-month growth that stay right when rows are missing

Before you start

On this page

LAG and LEAD are window functions that read a value from another row of the same window. lag(revenue) returns the revenue of the row before the current one, and lead(revenue) that of the row after it, in the order set by the window’s ORDER BY. FIRST_VALUE and NTH_VALUE read a value from a fixed position instead: the first row of the window frame, or its nth row. With them a query can set each month next to the month before, count the days since a customer’s previous order or show the product each customer bought first, all without joining the table to itself. They work in PostgreSQL, in MySQL 8.0 and later and in SQLite 3.25 and later, and the examples run in your browser on SQLite 3.49.1.

Looking at the row before and the row after

Here is a shop’s revenue for six months, each month next to the one before it and the one after it:

Each month with the previous and the next month's revenue SQL · month_over_month.sql
-- A shop's revenue by month, in rupees.
CREATE TABLE monthly (month TEXT PRIMARY KEY, revenue INTEGER NOT NULL);
INSERT INTO monthly VALUES
  ('2026-01', 52000), ('2026-02', 61000), ('2026-03', 58000),
  ('2026-04', 70000), ('2026-05', 66500), ('2026-06', 79800);

-- Each month next to the month before and the month after.
SELECT month, revenue,
       lag(revenue)  OVER (ORDER BY month)           AS prev_month,
       revenue - lag(revenue) OVER (ORDER BY month)  AS change,
       lead(revenue) OVER (ORDER BY month)           AS next_month
FROM monthly
ORDER BY month;

Output

┌─────────┬─────────┬────────────┬────────┬────────────┐
│  month  │ revenue │ prev_month │ change │ next_month │
├─────────┼─────────┼────────────┼────────┼────────────┤
│ 2026-01 │ 52000   │            │        │ 61000      │
│ 2026-02 │ 61000   │ 52000      │ 9000   │ 58000      │
│ 2026-03 │ 58000   │ 61000      │ -3000  │ 70000      │
│ 2026-04 │ 70000   │ 58000      │ 12000  │ 66500      │
│ 2026-05 │ 66500   │ 70000      │ -3500  │ 79800      │
│ 2026-06 │ 79800   │ 66500      │ 13300  │            │
└─────────┴─────────┴────────────┴────────┴────────────┘

Recorded with sql.js 1.14.2 (SQLite 3.49.1) on macOS 26 arm64. To run it yourself: sqlite3 -box :memory: < month_over_month.sql

LAG found January’s 52000 for February, so February’s change is 9000, and LEAD found February’s 61000 for January. The first month has no month before it, so its LAG is NULL and so is its change; the last month has no LEAD. Both functions take two more arguments: lag(revenue, 3) reads three rows back, and a third argument replaces the NULL, as in lag(revenue, 1, 0). Nothing has to be numbered or joined first: the window’s ORDER BY alone decides what “before” means, so over ORDER BY month DESC the same LAG would read the following month. With a PARTITION BY, every partition starts afresh, and the first row of each partition has no LAG of its own.

Six months in order with April as the current row: which month LAG, LEAD, FIRST_VALUE, NTH_VALUE and LAST_VALUE read.The default frame on April's row:from the first row to the current rowMay · 66500LEAD(revenue)June · 79800LEAD(revenue, 2)January · 52000FIRST_VALUE(revenue)February · 61000NTH_VALUE(revenue, 2)March · 58000LAG(revenue)April · 70000the current rowLAST_VALUE(revenue)

The rows that the value functions read on April's row

Text description of the diagram

The diagram lists the six months of the lesson's monthly revenue in order, with April as the current row, and labels each month with the function that reads it.

  1. January, 52000: FIRST_VALUE(revenue) reads the first row of the frame.
  2. February, 61000: NTH_VALUE(revenue, 2) reads the second row of the frame.
  3. March, 58000: LAG(revenue) reads the row just before the current one.
  4. April, 70000: the current row. With the default frame, which runs from the first row to the current row, LAST_VALUE(revenue) reads April itself.
  5. May, 66500: LEAD(revenue) reads the row just after the current one.
  6. June, 79800: LEAD(revenue, 2) reads the row two places after it.

January to April are drawn inside a box that marks the default frame on April's row. LAG and LEAD look at the rows of the whole partition, inside or outside the frame; FIRST_VALUE, NTH_VALUE and LAST_VALUE read only the frame.

The five functions of this lesson, one line each:

  • LAG reads a value from a row before the current one: one row back, unless an offset says more.
  • LEAD reads a value from a row after the current one, in the same way.
  • FIRST_VALUE reads the value of the first row of the window frame.
  • LAST_VALUE reads the last row of the frame; by default that is the current row or its last tie, not the partition’s last row.
  • NTH_VALUE reads the nth row of the frame, and NULL while the frame has fewer than n rows.

LAG and LEAD always look at the whole partition, whatever the frame. FIRST_VALUE, LAST_VALUE and NTH_VALUE look only at the frame, which by default runs from the first row of the partition to the current row and its ties. That one difference explains most surprises with these functions.

Growth rates, offsets and defaults, with numbers Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Days between orders, and each customer’s first product
  • When the previous row is not the previous month
  • LAST_VALUE and a frame that stops at the current row
  • Missing values: IGNORE NULLS and a portable fill
  • The same functions in PostgreSQL, MySQL and SQLite
  • Your turn: days since the previous login
    • Days since each user's previous login, and long gaps
  • Check yourself
  • Interview questions
    • What do LAG and LEAD return on the first and the last row of a partition, and how do you change that?
    • A month-over-month revenue report uses LAG. The shop sold nothing in April and has no row for it, and the report shows May as 14.7 % up on the previous month. What is wrong, how do you fix it, and what should April and May show?
    • For each user, flag the logins that come more than 7 days after the same user's previous login, then count the users who came back at least once after such a gap. Users with a single login must not be flagged, and a user may log in several times a day.
    • LAST_VALUE(price) OVER (PARTITION BY product ORDER BY changed_on) returns each row's own price instead of the product's latest price. Why, and what are three ways to show the latest price on every row?
  • Key takeaways

5 runnable examples (SQL) · 1 exercise with sample tests · 3 interview answers · about 22 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.