18. LAG and LEAD#
18.1. Synopsis#
LAG(
<expression>
[, <offset> [, <default_value>]
) OVER()
LEAD(
<expression>
[, <offset> [, <default_value>]
) OVER()
-- Optional
OVER (PARTITION BY <partition_expression>, ...)
18.2. Description#
LAG and LEAD are window functions that return values from rows
relative to the current row.
LAGreturns a value from a previous rowLEADreturns a value from a following row
These functions are typically used for time-series and analytical queries, such as computing deltas, changes over time, or comparing adjacent values.
In qdbsh, the OVER() clause is required when using LAG or LEAD.
The supported OVER() syntax is intentionally limited and differs from
standard SQL window functions.
18.3. Parameters#
expressionColumn or expression whose value is returned from another row.
offsetNumber of rows backward (for
LAG) or forward (forLEAD). Defaults to1.default_valueValue returned when the requested row does not exist. Defaults to
NULL.partition_expressionOne or more expressions that divide the result set into independent partitions. Window functions are evaluated separately within each partition.
18.4. Notes#
OVER() is required
LAG and LEAD must be used with an OVER() clause. Omitting
OVER() results in a syntax error.
Ordering behavior
ORDER BY is not supported inside OVER().
Row ordering is defined by the outer query’s ORDER BY clause.
Partitioning behavior
When PARTITION BY is used, LAG and LEAD operate only within
each partition. If omitted, the entire result set is treated as a single
partition.
18.5. Examples#
18.5.1. Previous and next values#
Assume a time-series table:
table: bac_stock
column: Adj_Close (DOUBLE)
Retrieve the previous value using LAG:
SELECT
$timestamp,
Adj_Close,
LAG(Adj_Close, 1) OVER() AS previous_price
FROM bac_stock
ORDER BY $timestamp;
Retrieve the next value using LEAD:
SELECT
$timestamp,
Adj_Close,
LEAD(Adj_Close, 1) OVER() AS next_price
FROM bac_stock
ORDER BY $timestamp;
18.5.2. Day-over-day change#
Compute a price delta using LAG:
SELECT
$timestamp,
Adj_Close,
LAG(Adj_Close, 1) OVER() AS previous_price,
Adj_Close - LAG(Adj_Close, 1) OVER() AS price_change
FROM bac_stock
ORDER BY $timestamp;
18.5.3. Using default values#
Provide a fallback value when no previous row exists:
SELECT
$timestamp,
Adj_Close,
LAG(Adj_Close, 1, 0) OVER() AS previous_price
FROM bac_stock
ORDER BY $timestamp;
18.5.4. Partitioned window#
Compute lagged values independently per symbol:
SELECT
symbol,
$timestamp,
price,
LAG(price, 1) OVER (PARTITION BY symbol) AS previous_price
FROM stock_prices
ORDER BY symbol, $timestamp;
In this query:
Rows are grouped by
symbolEach symbol’s series is processed independently
Row order is defined by the outer
ORDER BYclause
18.6. Usage Guidelines#
Use categorical identifiers (such as
symbol,metric,device_id) withPARTITION BY.Avoid partitioning by the same numeric value being lagged or led, as this often results in single-row partitions.
18.7. Example Usage#
This section provides a complete, runnable example demonstrating how to use
LAG and LEAD in qdbsh, from table creation to analytical queries.
18.7.1. Setup#
Create a simple time-series table and insert sample data:
CREATE TABLE bac_stock (
Adj_Close DOUBLE
);
INSERT INTO bac_stock ($timestamp, Adj_Close) VALUES
(2022-01-01, 10),
(2022-01-02, 20),
(2022-01-03, 30),
(2022-01-04, 25),
(2022-01-05, 28);
18.7.2. Simple LEAD example#
Retrieve the next row’s value using LEAD:
SELECT
$timestamp,
Adj_Close AS price,
LEAD(Adj_Close, 1) OVER() AS next_price
FROM bac_stock
ORDER BY $timestamp;
This query returns the price of the following row for each timestamp.
For the last row, next_price is NULL because no subsequent row exists.
18.7.3. Price change and percent change#
Compute both the absolute and percentage change using LAG:
SELECT
$timestamp,
Adj_Close AS price,
LAG(Adj_Close, 1) OVER() AS prev_price,
(Adj_Close - LAG(Adj_Close, 1) OVER()) AS change,
((Adj_Close / LAG(Adj_Close, 1) OVER()) - 1) AS change_pct
FROM bac_stock
ORDER BY $timestamp;
In this query:
prev_pricecontains the previous row’s valuechangecomputes the absolute difference from the previous rowchange_pctcomputes the relative change from the previous row
The first row has no previous value, so prev_price, change, and
change_pct are NULL for that row.
18.8. Partitioning Example#
This example demonstrates how to use PARTITION BY to compute previous/next
values independently for each series.
18.8.1. Setup#
Create a multi-series time-series table (partition key: symbol):
CREATE TABLE stock_prices (
symbol STRING,
price DOUBLE
);
INSERT INTO stock_prices ($timestamp, symbol, price) VALUES
(2022-01-01, 'AAA', 10),
(2022-01-02, 'AAA', 12),
(2022-01-03, 'AAA', 11),
(2022-01-01, 'BBB', 20),
(2022-01-02, 'BBB', 25),
(2022-01-03, 'BBB', 30);
18.8.2. Simple LEAD with PARTITION BY#
Retrieve the next value within each symbol series:
SELECT
symbol,
$timestamp,
price,
LEAD(price, 1) OVER (PARTITION BY symbol) AS next_price
FROM stock_prices
ORDER BY symbol, $timestamp;
18.8.3. Price change and percent change per partition#
Compute deltas independently per symbol:
SELECT
symbol,
$timestamp,
price,
LAG(price, 1) OVER (PARTITION BY symbol) AS prev_price,
(price - LAG(price, 1) OVER (PARTITION BY symbol)) AS change,
((price / LAG(price, 1) OVER (PARTITION BY symbol)) - 1) AS change_pct
FROM stock_prices
ORDER BY symbol, $timestamp;
In this example:
PARTITION BY symbolensures thatAAAnever uses values fromBBB(and vice versa).Row order is defined by the outer
ORDER BY symbol, $timestampclause.The first row in each partition has no previous row, so lag-based fields are
NULLfor that row.
18.9. Summary#
LAGandLEADare supported window functions in qdbshOVER()is requiredPARTITION BYis supportedORDER BYinsideOVER()is not supportedRow ordering is defined by the outer query