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:
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:
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-27Compute a field from values in preceding or following rows: running totals, row differences
Apache DataFusion (5)
first_valueread 2026-09-27Window: Value at the first row of the window frame
lagread 2026-09-27Window: Value at a row offset before the current row in the partition, with a default
last_valueread 2026-09-27Window: Value at the last row of the window frame
leadread 2026-09-27Window: Value at a row offset after the current row in the partition, with a default
nth_valueread 2026-09-27Window: Value at the nth row of the window frame
Apache Hop (1)
Analytic Queryread 2026-09-27Lead, lag, first and last values across sorted rows within groups
Bruin (2)
DuckDB (5)
first_valueread 2026-09-27first_value aggregate
lagread 2026-09-27lag aggregate
last_valueread 2026-09-27last_value aggregate
leadread 2026-09-27lead aggregate
nth_valueread 2026-09-27nth_value aggregate
Duckle (4)
First Valueread 2026-09-25FIRST_VALUE over a window
Lagread 2026-09-25LAG value over a window
Last Valueread 2026-09-25LAST_VALUE over a window
Leadread 2026-09-25LEAD value over a window
EasyMorph (1)
Shift column up/downread 2026-09-27Shift a column's values up or down by rows, leaving empty cells at the vacated end
Ibis (3)
KNIME Analytics Platform (1)
Lag Columnread 2026-09-27Copy values from preceding rows into the current row as one or more lagged columns
Pentaho Data Integration (1)
Analytic queryread 2026-09-27Lead, lag, first and last values over a sorted dataset
Polars (4)
DataFrame.shiftread 2026-09-27Shift every column by n rows, filling the gap with null or a fill value
Expr.diffread 2026-09-27Difference between each value and the value n rows before it
Expr.pct_changeread 2026-09-27Percentage change between each value and the value n rows before it
Expr.shiftread 2026-09-27Shift values by n rows, filling the gap with null or a fill value
RapidMiner Studio (2)
Tableau Prep (3)
Difference Fromread 2026-09-27Difference between the current row's value and the value N rows before or after
LOOKUPread 2026-09-27Value of an expression in the row at a relative offset from the current row
Percent Difference Fromread 2026-09-27Percentage difference between the current row's value and the value N rows away
Talend (1)
tMemorizeRowsread 2026-09-27Hold the last N rows in an indexed array so later components can refer to earlier rows
Trifacta (2)
NEXT Functionread 2026-09-27Value of a column a given number of rows after the current row
PREV Functionread 2026-09-27Value 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]
Sources
- [1]Window function (SQL) - Wikipediasecondary source
- [2]PostgreSQL: Documentation: 18: 9.22. Window Functionsdocumentation
- [3]Window Functions – DuckDBdocumentation