Shape / table · All operations

Take a value from another row

A variant of Calculate across rows

Bring a value from a row a fixed offset before or after the current one, or from the first, last or nth row of the window, often to compute a difference or percentage change.

Status

In arcform

Runs as a SQL step

arcform carries it as a sql: step today.

The DuckDB step

Take a value from another row: the previous, the next, the first, the last and the nth

It reads the orders table the proofs share. Create it first:

SQL
CREATE TABLE orders AS SELECT * FROM (VALUES
  (1,  'Alice Ng',      'north', DATE '2026-01-03', 120.00, 'widget', 'a|b',   '{"channel":"web","items":[1,2]}'),
  (2,  'alice ng',      'north', DATE '2026-01-05',  80.50, 'gadget', 'b',     '{"channel":"shop","items":[3]}'),
  (3,  'Bob  Okafor',   'south', DATE '2026-01-05', 300.00, 'widget', 'a|c',   '{"channel":"web","items":[4,5,6]}'),
  (4,  'Carla Díaz',    'east',  DATE '2026-01-09',  NULL,  'gizmo',  NULL,    '{"channel":"web","items":[]}'),
  (5,  'Bob Okafor',    'south', DATE '2026-01-12',  45.25, 'gadget', 'c',     '{"channel":"phone","items":[7]}'),
  (6,  'Dev Patel',     'west',  DATE '2026-01-12', 510.00, 'widget', 'a',     '{"channel":"web","items":[8,9]}'),
  (7,  'Carla Diaz',    'east',  DATE '2026-01-20',  99.99, 'gizmo',  'b|c',   '{"channel":"shop","items":[10]}'),
  (8,  'Dev Patel',     'west',  DATE '2026-02-02', 510.00, 'widget', 'a',     '{"channel":"web","items":[8,9]}'),
  (9,  'Eve Larsen',    NULL,    DATE '2026-02-03',  15.00, 'gadget', NULL,    '{"channel":"web","items":[11]}'),
  (10, 'Alice Ng',      'north', DATE '2026-02-10', 220.00, 'gizmo',  'a|b|c', '{"channel":"phone","items":[12,13]}')
) t(order_id, customer, region, order_date, amount, product, tags, meta);

Then run the statement:

SQL
CREATE TEMP TABLE result AS
SELECT product, order_id, amount,
       lag(amount) OVER w AS previous_amount,
       lead(amount) OVER w AS next_amount,
       round(amount / lag(amount) OVER w - 1, 4) AS change,
       first_value(amount) OVER whole AS first_amount,
       last_value(amount) OVER whole AS last_amount,
       nth_value(amount, 2) OVER whole AS second_amount
FROM orders
WHERE amount IS NOT NULL
WINDOW w AS (PARTITION BY product ORDER BY order_date, order_id),
       whole AS (PARTITION BY product ORDER BY order_date, order_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
SELECT * FROM result ORDER BY product, order_id;
SELECT CASE WHEN ((SELECT list(previous_amount ORDER BY order_id) FROM result WHERE product = 'widget') IS NOT DISTINCT FROM [NULL, 120.00, 300.00, 510.00] AND (SELECT list(next_amount ORDER BY order_id) FROM result WHERE product = 'widget') IS NOT DISTINCT FROM [300.00, 510.00, 510.00, NULL] AND (SELECT list(change ORDER BY order_id) FROM result WHERE product = 'widget') IS NOT DISTINCT FROM [NULL, 1.5, 0.7, 0.0] AND (SELECT list(change ORDER BY order_id) FROM result WHERE product = 'gadget') IS NOT DISTINCT FROM [NULL, -0.4379, -0.6685] AND (SELECT list(change ORDER BY order_id) FROM result WHERE product = 'gizmo') IS NOT DISTINCT FROM [NULL, 1.2002] AND (SELECT (first_amount, last_amount, second_amount) FROM result WHERE order_id = 6) = (120.00, 510.00, 300.00) AND (SELECT (first_amount, last_amount, second_amount) FROM result WHERE order_id = 5) = (80.50, 15.00, 45.25)) THEN 'as expected' ELSE error('the previous, next, first, last and second amounts or the change from the previous order were not the expected values per product') END AS verdict;
  • It needs nothing installed beyond DuckDB itself.
  • The statement checks its own result and raises an error when it is wrong: “the previous, next, first, last and second amounts or the change from the previous order were not the expected values per product”.
  • It passed on DuckDB v2.0.0 (Cyanoptera) d54cbbe3df.

What other tools call it

Meridian's classification; an independent blind rating agreed on 95.7% of 324 rows (variant, version 3).

Alteryx Designer (1)
  • Multi-Row Formularead 2026-09-27

    Compute a field from values in preceding or following rows: running totals, row differences

Apache DataFusion (5)
  • first_valueread 2026-09-27

    Window: Value at the first row of the window frame

  • lagread 2026-09-27

    Window: Value at a row offset before the current row in the partition, with a default

  • last_valueread 2026-09-27

    Window: Value at the last row of the window frame

  • leadread 2026-09-27

    Window: Value at a row offset after the current row in the partition, with a default

  • nth_valueread 2026-09-27

    Window: Value at the nth row of the window frame

Apache Hop (1)
  • Analytic Queryread 2026-09-27

    Lead, lag, first and last values across sorted rows within groups

Bruin (2)
  • lagread 2026-09-27

    Previous value of a referenced metric, offset 1 by default

  • leadread 2026-09-27

    Next value of a referenced metric, offset 1 by default

DuckDB (5)
  • first_valueread 2026-09-27

    first_value aggregate

  • lagread 2026-09-27

    lag aggregate

  • last_valueread 2026-09-27

    last_value aggregate

  • leadread 2026-09-27

    lead aggregate

  • nth_valueread 2026-09-27

    nth_value aggregate

Duckle (4)
  • First Valueread 2026-09-25

    FIRST_VALUE over a window

  • Lagread 2026-09-25

    LAG value over a window

  • Last Valueread 2026-09-25

    LAST_VALUE over a window

  • Leadread 2026-09-25

    LEAD value over a window

EasyMorph (1)
  • Shift column up/downread 2026-09-27

    Shift a column's values up or down by rows, leaving empty cells at the vacated end

Ibis (3)
  • Lagread 2026-09-27

    Value from a row a fixed offset earlier in the ordered window

  • Leadread 2026-09-27

    Value from a row a fixed offset later in the ordered window

  • NthValueread 2026-09-27

    Pick the Nth value of a column within the window

KNIME Analytics Platform (1)
  • Lag Columnread 2026-09-27

    Copy values from preceding rows into the current row as one or more lagged columns

Pentaho Data Integration (1)
  • Analytic queryread 2026-09-27

    Lead, lag, first and last values over a sorted dataset

Polars (4)
  • DataFrame.shiftread 2026-09-27

    Shift every column by n rows, filling the gap with null or a fill value

  • Expr.diffread 2026-09-27

    Difference between each value and the value n rows before it

  • Expr.pct_changeread 2026-09-27

    Percentage change between each value and the value n rows before it

  • Expr.shiftread 2026-09-27

    Shift values by n rows, filling the gap with null or a fill value

RapidMiner Studio (2)
  • Lagread 2026-09-27

    Shift series columns by a number of rows to create lagged copies; negative lags allowed

  • Windowingread 2026-09-27

    Turn a series into rows of windowed lag columns plus optional horizon columns

Tableau Prep (3)
  • Difference Fromread 2026-09-27

    Difference between the current row's value and the value N rows before or after

  • LOOKUPread 2026-09-27

    Value of an expression in the row at a relative offset from the current row

  • Percentage difference between the current row's value and the value N rows away

Talend (1)
  • tMemorizeRowsread 2026-09-27

    Hold the last N rows in an indexed array so later components can refer to earlier rows

Trifacta (2)
  • NEXT Functionread 2026-09-27

    Value of a column a given number of rows after the current row

  • PREV Functionread 2026-09-27

    Value of a column a given number of rows before the current row

VisiData (1)
  • addcol-deltaread 2026-09-23

    (experimental) Add column with the difference from the previous row in current column

Background

Window functions allow access to the data in the records right before and after the current record.[1] In PostgreSQL, lag returns a value from the row a given offset before the current row in its partition, or a default when no such row exists.[2] In DuckDB, amount - lag(amount) OVER (ORDER BY time) computes the difference between the current and the previous amount.[3]

When to use it

In PostgreSQL, first_value, last_value and nth_value consider only the rows within the window frame, which by default runs from the start of the partition through the last peer of the current row.[2] In PostgreSQL, the default frame is likely to give unhelpful results for last_value and sometimes for nth_value.[2] Give last_value a frame of ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING whenever it should return the partition's last row. (Meridian's view)

In PostgreSQL and DuckDB, when lag or lead is called without an offset or default, the offset is 1 and the default is NULL.[2][3] Before reading a lag difference as day-on-day, check that the previous row really is the previous day, since a missing day silently widens the gap. (Meridian's view)

In DuckDB, lag, lead, first_value, last_value and nth_value accept IGNORE NULLS, which counts only rows with a non-null value of the expression.[3] PostgreSQL does not implement the SQL standard's IGNORE NULLS option for lead, lag, first_value, last_value and nth_value, and always behaves as RESPECT NULLS.[2]

The method

In PostgreSQL, lag, lead, first_value, last_value and nth_value all depend on the sort order given by the ORDER BY of the window definition.[2] In PostgreSQL, the offset and the default of lag and lead are both evaluated with respect to the current row.[2] In PostgreSQL and DuckDB, nth_value counts from 1 within the window frame and returns NULL if there is no such row.[2][3]