From 1fc5c0168dfaf9bef6c629f54c710b76a217455f Mon Sep 17 00:00:00 2001 From: javier Date: Thu, 5 Feb 2026 13:24:43 +0100 Subject: [PATCH 1/4] Document WINDOW clause for named window definitions Add documentation for the new WINDOW clause that allows defining window frames once and referencing them by name. This reduces repetition when multiple window functions use the same frame. - Add new "Named windows" section to syntax.md with examples - Update overview.md with brief mention and syntax example - Update reference.md trade frequency example to use named window - Refactor cookbook examples to use named windows: - bollinger-bands.md - bollinger-bandwidth.md - vwap.md - overlay-timeshift.md --- .../integrations/grafana/overlay-timeshift.md | 14 ++- .../cookbook/sql/finance/bollinger-bands.md | 32 +++--- .../sql/finance/bollinger-bandwidth.md | 13 +-- documentation/cookbook/sql/finance/vwap.md | 12 +-- .../functions/window-functions/overview.md | 33 ++++--- .../functions/window-functions/reference.md | 20 ++-- .../functions/window-functions/syntax.md | 98 ++++++++++++++++++- 7 files changed, 148 insertions(+), 74 deletions(-) diff --git a/documentation/cookbook/integrations/grafana/overlay-timeshift.md b/documentation/cookbook/integrations/grafana/overlay-timeshift.md index aac4ca768..4bae77eb8 100644 --- a/documentation/cookbook/integrations/grafana/overlay-timeshift.md +++ b/documentation/cookbook/integrations/grafana/overlay-timeshift.md @@ -28,11 +28,10 @@ WITH sampled AS ( AND symbol = @symbol ), cumulative AS ( SELECT timestamp, symbol, - SUM(traded_value) - OVER (ORDER BY timestamp) AS cumulative_value, - SUM(volume) - OVER (ORDER BY timestamp) AS cumulative_volume + SUM(traded_value) OVER w AS cumulative_value, + SUM(volume) OVER w AS cumulative_volume FROM sampled + WINDOW w AS (ORDER BY timestamp) ) SELECT timestamp as time, cumulative_value/cumulative_volume AS vwap_yesterday FROM cumulative; ``` @@ -52,11 +51,10 @@ WITH sampled AS ( AND symbol = @symbol ), cumulative AS ( SELECT timestamp, symbol, - SUM(traded_value) - OVER (ORDER BY timestamp) AS cumulative_value, - SUM(volume) - OVER (ORDER BY timestamp) AS cumulative_volume + SUM(traded_value) OVER w AS cumulative_value, + SUM(volume) OVER w AS cumulative_volume FROM sampled + WINDOW w AS (ORDER BY timestamp) ) SELECT dateadd('d',-1,timestamp) as time, cumulative_value/cumulative_volume AS vwap_today FROM cumulative; ``` diff --git a/documentation/cookbook/sql/finance/bollinger-bands.md b/documentation/cookbook/sql/finance/bollinger-bands.md index a9198748d..7dffe3354 100644 --- a/documentation/cookbook/sql/finance/bollinger-bands.md +++ b/documentation/cookbook/sql/finance/bollinger-bands.md @@ -36,15 +36,10 @@ WITH OHLC AS ( SELECT timestamp, close, - AVG(close) OVER ( - ORDER BY timestamp - ROWS 19 PRECEDING - ) AS sma20, - AVG(close * close) OVER ( - ORDER BY timestamp - ROWS 19 PRECEDING - ) AS avg_close_sq + AVG(close) OVER w AS sma20, + AVG(close * close) OVER w AS avg_close_sq FROM OHLC + WINDOW w AS (ORDER BY timestamp ROWS 19 PRECEDING) ) SELECT timestamp, @@ -74,9 +69,11 @@ The core of the Bollinger Bands calculation is the rolling standard deviation. P **Different period lengths:** ```sql --- 10-period Bollinger Bands (change 19 to 9) -AVG(close) OVER (ORDER BY timestamp ROWS 9 PRECEDING) AS sma10, -AVG(close * close) OVER (ORDER BY timestamp ROWS 9 PRECEDING) AS avg_close_sq +-- 10-period Bollinger Bands (change ROWS 19 to ROWS 9) +AVG(close) OVER w AS sma10, +AVG(close * close) OVER w AS avg_close_sq +... +WINDOW w AS (ORDER BY timestamp ROWS 9 PRECEDING) ``` **Different band multipliers:** @@ -116,17 +113,10 @@ WITH OHLC AS ( timestamp, symbol, close, - AVG(close) OVER ( - PARTITION BY symbol - ORDER BY timestamp - ROWS 19 PRECEDING - ) AS sma20, - AVG(close * close) OVER ( - PARTITION BY symbol - ORDER BY timestamp - ROWS 19 PRECEDING - ) AS avg_close_sq + AVG(close) OVER w AS sma20, + AVG(close * close) OVER w AS avg_close_sq FROM OHLC + WINDOW w AS (PARTITION BY symbol ORDER BY timestamp ROWS 19 PRECEDING) ) SELECT timestamp, diff --git a/documentation/cookbook/sql/finance/bollinger-bandwidth.md b/documentation/cookbook/sql/finance/bollinger-bandwidth.md index 2dacba97d..6617e7490 100644 --- a/documentation/cookbook/sql/finance/bollinger-bandwidth.md +++ b/documentation/cookbook/sql/finance/bollinger-bandwidth.md @@ -46,17 +46,10 @@ bands AS ( timestamp, symbol, close, - AVG(close) OVER ( - PARTITION BY symbol - ORDER BY timestamp - ROWS BETWEEN 19 PRECEDING AND CURRENT ROW - ) AS sma20, - AVG(close * close) OVER ( - PARTITION BY symbol - ORDER BY timestamp - ROWS BETWEEN 19 PRECEDING AND CURRENT ROW - ) AS avg_close_sq + AVG(close) OVER w AS sma20, + AVG(close * close) OVER w AS avg_close_sq FROM daily_ohlc + WINDOW w AS (PARTITION BY symbol ORDER BY timestamp ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) ), bollinger AS ( SELECT diff --git a/documentation/cookbook/sql/finance/vwap.md b/documentation/cookbook/sql/finance/vwap.md index 4f3e6776f..86cfa861c 100644 --- a/documentation/cookbook/sql/finance/vwap.md +++ b/documentation/cookbook/sql/finance/vwap.md @@ -32,9 +32,9 @@ WITH sampled AS ( ) SELECT timestamp, symbol, - SUM(traded_value) OVER (ORDER BY timestamp) / - SUM(total_volume) OVER (ORDER BY timestamp) AS vwap -FROM sampled; + SUM(traded_value) OVER w / SUM(total_volume) OVER w AS vwap +FROM sampled +WINDOW w AS (ORDER BY timestamp); ``` This query: @@ -68,9 +68,9 @@ WITH sampled AS ( ) SELECT timestamp, symbol, - SUM(traded_value) OVER (PARTITION BY symbol ORDER BY timestamp) / - SUM(total_volume) OVER (PARTITION BY symbol ORDER BY timestamp) AS vwap -FROM sampled; + SUM(traded_value) OVER w / SUM(total_volume) OVER w AS vwap +FROM sampled +WINDOW w AS (PARTITION BY symbol ORDER BY timestamp); ``` The `PARTITION BY symbol` ensures each symbol's VWAP is calculated independently. diff --git a/documentation/query/functions/window-functions/overview.md b/documentation/query/functions/window-functions/overview.md index ad727dd77..c53c5a300 100644 --- a/documentation/query/functions/window-functions/overview.md +++ b/documentation/query/functions/window-functions/overview.md @@ -25,7 +25,15 @@ function_name(arguments) OVER ( Some functions (`first_value`, `last_value`, `lag`, `lead`) also support `IGNORE NULLS` or `RESPECT NULLS` before the `OVER` keyword to control null handling. -For complete syntax details including frame specifications and exclusion options, see [OVER Clause Syntax](syntax.md). +When multiple window functions share the same definition, use the `WINDOW` clause to define it once: + +```sql +SELECT avg(price) OVER w, sum(amount) OVER w +FROM trades +WINDOW w AS (PARTITION BY symbol ORDER BY timestamp) +``` + +For complete syntax details including frame specifications, exclusion options, and named windows, see [OVER Clause Syntax](syntax.md). :::info Window function arithmetic (9.3.1+) Arithmetic operations on window functions (e.g., `sum(...) OVER (...) / sum(...) OVER (...)`) are supported from version 9.3.1. Earlier versions require wrapping window functions in CTEs or subqueries. @@ -257,16 +265,16 @@ WITH ohlc AS ( sum(amount) AS volume FROM trades WHERE timestamp IN '2024-05-22' AND symbol = @symbol - SAMPLE BY 1m ALIGN TO CALENDAR + SAMPLE BY 1m ) SELECT ts, symbol, open, high, low, close, volume, - sum((high + low + close) / 3 * volume) OVER (ORDER BY ts CUMULATIVE) - / sum(volume) OVER (ORDER BY ts CUMULATIVE) AS vwap + sum((high + low + close) / 3 * volume) OVER w / sum(volume) OVER w AS vwap FROM ohlc -ORDER BY ts; +ORDER BY ts +WINDOW w AS (ORDER BY ts CUMULATIVE); ``` ### Compare to group average @@ -276,10 +284,11 @@ SELECT symbol, price, timestamp, - avg(price) OVER (PARTITION BY symbol) AS symbol_avg, - price - avg(price) OVER (PARTITION BY symbol) AS diff_from_avg + avg(price) OVER w AS symbol_avg, + price - avg(price) OVER w AS diff_from_avg FROM trades -WHERE timestamp IN '[$today]'; +WHERE timestamp IN '[$today]' +WINDOW w AS (PARTITION BY symbol); ``` ### Rank within category @@ -303,11 +312,11 @@ WHERE timestamp IN '[$today]'; SELECT timestamp, price, - lag(price) OVER (ORDER BY timestamp) AS prev_price, - price - lag(price) OVER (ORDER BY timestamp) AS price_change + lag(price) OVER w AS prev_price, + price - lag(price) OVER w AS price_change FROM trades -WHERE timestamp IN '[$today]' - AND symbol = 'BTC-USDT'; +WHERE timestamp IN '[$today]' AND symbol = 'BTC-USDT' +WINDOW w AS (ORDER BY timestamp); ``` ## Next steps diff --git a/documentation/query/functions/window-functions/reference.md b/documentation/query/functions/window-functions/reference.md index 1756efa70..b9d67e33c 100644 --- a/documentation/query/functions/window-functions/reference.md +++ b/documentation/query/functions/window-functions/reference.md @@ -912,24 +912,18 @@ WHERE timestamp IN '[$today]' AND symbol = 'GBPUSD'; ### Trade frequency analysis +This example uses a [named window](syntax.md#named-windows-window-clause) to avoid repeating the same window definition: + ```questdb-sql title="Trades per minute by side" demo SELECT timestamp, symbol, - COUNT(*) OVER ( - ORDER BY timestamp - RANGE BETWEEN 60000000 PRECEDING AND CURRENT ROW - ) AS updates_per_min, - COUNT(CASE WHEN side = 'buy' THEN 1 END) OVER ( - ORDER BY timestamp - RANGE BETWEEN 60000000 PRECEDING AND CURRENT ROW - ) AS buys_per_minute, - COUNT(CASE WHEN side = 'sell' THEN 1 END) OVER ( - ORDER BY timestamp - RANGE BETWEEN 60000000 PRECEDING AND CURRENT ROW - ) AS sells_per_minute + COUNT(*) OVER w AS updates_per_min, + COUNT(CASE WHEN side = 'buy' THEN 1 END) OVER w AS buys_per_minute, + COUNT(CASE WHEN side = 'sell' THEN 1 END) OVER w AS sells_per_minute FROM trades -WHERE timestamp IN '[$today]' AND symbol = 'BTC-USDT'; +WHERE timestamp IN '[$today]' AND symbol = 'BTC-USDT' +WINDOW w AS (ORDER BY timestamp RANGE BETWEEN 60000000 PRECEDING AND CURRENT ROW); ``` --- diff --git a/documentation/query/functions/window-functions/syntax.md b/documentation/query/functions/window-functions/syntax.md index 0de78a027..746e8e777 100644 --- a/documentation/query/functions/window-functions/syntax.md +++ b/documentation/query/functions/window-functions/syntax.md @@ -332,13 +332,13 @@ WITH ohlc AS ( sum(amount) AS volume FROM trades WHERE timestamp IN '2024-05-22' AND symbol = @symbol - SAMPLE BY 1m ALIGN TO CALENDAR + SAMPLE BY 1m ) SELECT ts, open, high, low, close, volume, - sum((high + low + close) / 3 * volume) OVER (ORDER BY ts CUMULATIVE) - / sum(volume) OVER (ORDER BY ts CUMULATIVE) AS vwap -FROM ohlc; + sum((high + low + close) / 3 * volume) OVER w / sum(volume) OVER w AS vwap +FROM ohlc +WINDOW w AS (ORDER BY ts CUMULATIVE); ``` ### Frame shorthand syntax @@ -463,6 +463,96 @@ LIMIT 100; - Consider **index usage** when ordering by timestamp columns - Narrow windows process less data than wide windows +## Named windows (WINDOW clause) + +When multiple window functions share the same window definition, you can define the window once and reference it by name. This reduces repetition and improves readability. + +**Syntax:** +```sql +SELECT + columns, + window_function() OVER window_name, + another_function() OVER window_name +FROM table +ORDER BY column +WINDOW window_name AS (window_definition) [, ...] +LIMIT n; +``` + +The `WINDOW` clause appears after `ORDER BY` and before `LIMIT`. + +**Example:** +```questdb-sql title="Named window for repeated definitions" demo +SELECT + timestamp, + symbol, + price, + avg(price) OVER w AS avg_price, + min(price) OVER w AS min_price, + max(price) OVER w AS max_price +FROM trades +WHERE timestamp IN '[$today]' AND symbol = 'BTC-USDT' +WINDOW w AS (ORDER BY timestamp ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) +LIMIT 100; +``` + +### Multiple named windows + +You can define multiple windows in a single `WINDOW` clause: + +```questdb-sql title="Multiple named windows" demo +SELECT + timestamp, + symbol, + price, + avg(price) OVER short_window AS avg_10, + avg(price) OVER long_window AS avg_50 +FROM trades +WHERE timestamp IN '[$today]' AND symbol = 'BTC-USDT' +WINDOW + short_window AS (ORDER BY timestamp ROWS BETWEEN 9 PRECEDING AND CURRENT ROW), + long_window AS (ORDER BY timestamp ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) +LIMIT 100; +``` + +### Mixing inline and named windows + +You can use both named windows and inline `OVER (...)` definitions in the same query: + +```questdb-sql title="Mixed inline and named windows" demo +SELECT + timestamp, + symbol, + price, + avg(price) OVER w AS moving_avg, + row_number() OVER (PARTITION BY symbol ORDER BY timestamp) AS seq +FROM trades +WHERE timestamp IN '[$today]' AND symbol = 'BTC-USDT' +WINDOW w AS (ORDER BY timestamp ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) +LIMIT 100; +``` + +### Works with CTEs and subqueries + +Named windows work within CTEs and subqueries: + +```questdb-sql title="Named window in CTE" demo +WITH price_stats AS ( + SELECT + timestamp, + symbol, + price, + avg(price) OVER w AS moving_avg, + price - avg(price) OVER w AS deviation + FROM trades + WHERE timestamp IN '[$today]' AND symbol = 'BTC-USDT' + WINDOW w AS (ORDER BY timestamp ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) +) +SELECT * FROM price_stats +WHERE deviation > 10 +LIMIT 100; +``` + ## Common pitfalls ### Window functions in WHERE From 7c42a4d95df93f6dc7564d149b1e8bdf4c6a412e Mon Sep 17 00:00:00 2001 From: javier Date: Thu, 5 Feb 2026 18:42:24 +0100 Subject: [PATCH 2/4] Fix missed WINDOW clause opportunities in cookbook recipes --- documentation/cookbook/sql/finance/bollinger-bandwidth.md | 7 ++++--- documentation/cookbook/sql/finance/vwap.md | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/documentation/cookbook/sql/finance/bollinger-bandwidth.md b/documentation/cookbook/sql/finance/bollinger-bandwidth.md index 6617e7490..54b3b7285 100644 --- a/documentation/cookbook/sql/finance/bollinger-bandwidth.md +++ b/documentation/cookbook/sql/finance/bollinger-bandwidth.md @@ -49,7 +49,7 @@ bands AS ( AVG(close) OVER w AS sma20, AVG(close * close) OVER w AS avg_close_sq FROM daily_ohlc - WINDOW w AS (PARTITION BY symbol ORDER BY timestamp ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) + WINDOW w AS (PARTITION BY symbol ORDER BY timestamp ROWS 19 PRECEDING) ), bollinger AS ( SELECT @@ -81,9 +81,10 @@ with_range AS ( upper_band, lower_band, bandwidth, - min(bandwidth) OVER (PARTITION BY symbol) AS min_bw, - max(bandwidth) OVER (PARTITION BY symbol) AS max_bw + min(bandwidth) OVER w AS min_bw, + max(bandwidth) OVER w AS max_bw FROM with_bandwidth + WINDOW w AS (PARTITION BY symbol) ) SELECT timestamp, diff --git a/documentation/cookbook/sql/finance/vwap.md b/documentation/cookbook/sql/finance/vwap.md index 86cfa861c..7cbd08b3e 100644 --- a/documentation/cookbook/sql/finance/vwap.md +++ b/documentation/cookbook/sql/finance/vwap.md @@ -43,11 +43,12 @@ This query: ## How it works -The key insight is using `SUM(...) OVER (ORDER BY timestamp)` to create running totals, then dividing them directly: +The key insight is using `SUM(...) OVER w` with a named window to create running totals, then dividing them directly: ```sql -SUM(traded_value) OVER (ORDER BY timestamp) / - SUM(total_volume) OVER (ORDER BY timestamp) AS vwap +SUM(traded_value) OVER w / SUM(total_volume) OVER w AS vwap +... +WINDOW w AS (ORDER BY timestamp) ``` When using `SUM() OVER (ORDER BY timestamp)` without specifying a frame clause, QuestDB defaults to summing from the first row to the current row, which is exactly what we need for cumulative VWAP. From 034e467282f064523005ba2f238e885442d25e4f Mon Sep 17 00:00:00 2001 From: javier Date: Mon, 9 Feb 2026 11:40:31 +0100 Subject: [PATCH 3/4] Fix WINDOW clause position to before ORDER BY per SQL standard --- .../query/functions/window-functions/overview.md | 4 ++-- documentation/query/functions/window-functions/syntax.md | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/documentation/query/functions/window-functions/overview.md b/documentation/query/functions/window-functions/overview.md index c53c5a300..18ad55c94 100644 --- a/documentation/query/functions/window-functions/overview.md +++ b/documentation/query/functions/window-functions/overview.md @@ -273,8 +273,8 @@ SELECT open, high, low, close, volume, sum((high + low + close) / 3 * volume) OVER w / sum(volume) OVER w AS vwap FROM ohlc -ORDER BY ts -WINDOW w AS (ORDER BY ts CUMULATIVE); +WINDOW w AS (ORDER BY ts CUMULATIVE) +ORDER BY ts; ``` ### Compare to group average diff --git a/documentation/query/functions/window-functions/syntax.md b/documentation/query/functions/window-functions/syntax.md index 746e8e777..5109afe4a 100644 --- a/documentation/query/functions/window-functions/syntax.md +++ b/documentation/query/functions/window-functions/syntax.md @@ -56,6 +56,10 @@ EXCLUDE CURRENT ROW | EXCLUDE NO OTHERS | `CUMULATIVE` | Shorthand for `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` | | `EXCLUDE` | Optionally excludes rows from the frame | +:::tip Reusing window definitions +If multiple window functions in the same query share the same frame clause, you can define it once using a [named window](#named-windows-window-clause) instead of repeating it. +::: + ## PARTITION BY `PARTITION BY` divides the result set into groups. The window function operates independently on each partition. @@ -474,12 +478,12 @@ SELECT window_function() OVER window_name, another_function() OVER window_name FROM table -ORDER BY column WINDOW window_name AS (window_definition) [, ...] +ORDER BY column LIMIT n; ``` -The `WINDOW` clause appears after `ORDER BY` and before `LIMIT`. +The `WINDOW` clause appears after `WHERE` and before `ORDER BY`. **Example:** ```questdb-sql title="Named window for repeated definitions" demo From b7527f367317192719f3a5776e2da4b5503fcb46 Mon Sep 17 00:00:00 2001 From: bluestreak Date: Wed, 11 Feb 2026 17:32:59 +0000 Subject: [PATCH 4/4] Add window inheritance docs and restructure syntax page Co-Authored-By: Claude Opus 4.6 --- .../functions/window-functions/syntax.md | 86 +++++++++++++++---- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/documentation/query/functions/window-functions/syntax.md b/documentation/query/functions/window-functions/syntax.md index 5109afe4a..c755db1b2 100644 --- a/documentation/query/functions/window-functions/syntax.md +++ b/documentation/query/functions/window-functions/syntax.md @@ -1,21 +1,27 @@ --- -title: OVER Clause Syntax -sidebar_label: OVER Syntax -description: Complete syntax reference for the OVER clause in window functions - partitioning, ordering, and frame specifications. -keywords: [over, partition by, order by, rows, range, frame, window functions] +title: Window Function Syntax +sidebar_label: Syntax +description: Complete syntax reference for window functions including the OVER clause, named windows with the WINDOW clause, and window inheritance. +keywords: [over, partition by, order by, rows, range, frame, window functions, window clause, named windows] --- -The `OVER` clause defines the window for a window function. This page covers the complete syntax for partitioning, ordering, and frame specifications. For an introduction to window functions, see the [Overview](overview.md). +The `OVER` clause defines the window for a window function. The `WINDOW` clause lets you define reusable named windows and build on them with inheritance. This page covers the complete syntax for both. For an introduction to window functions, see the [Overview](overview.md). ## Syntax ```sql +-- Inline window definition function_name(arguments) [IGNORE NULLS | RESPECT NULLS] OVER ( [PARTITION BY column [, ...]] [ORDER BY column [ASC | DESC] [, ...]] [frame_clause] [exclusion_clause] ) + +-- Named window definition +function_name(arguments) OVER window_name +... +WINDOW window_name AS ([base_window_name] [window_definition]) [, ...] ``` Where `frame_clause` is one of: @@ -56,9 +62,7 @@ EXCLUDE CURRENT ROW | EXCLUDE NO OTHERS | `CUMULATIVE` | Shorthand for `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` | | `EXCLUDE` | Optionally excludes rows from the frame | -:::tip Reusing window definitions -If multiple window functions in the same query share the same frame clause, you can define it once using a [named window](#named-windows-window-clause) instead of repeating it. -::: +Multiple window functions can share a definition using the [`WINDOW` clause](#named-windows-window-clause). ## PARTITION BY @@ -460,13 +464,6 @@ WHERE timestamp IN '[$today]' LIMIT 100; ``` -## Performance considerations - -- **ROWS frames** typically perform better than RANGE frames for large datasets -- **Partitioning** improves performance by processing smaller data chunks -- Consider **index usage** when ordering by timestamp columns -- Narrow windows process less data than wide windows - ## Named windows (WINDOW clause) When multiple window functions share the same window definition, you can define the window once and reference it by name. This reduces repetition and improves readability. @@ -557,6 +554,65 @@ WHERE deviation > 10 LIMIT 100; ``` +### Window inheritance + +A named window can reference another named window as its base, inheriting its `PARTITION BY`, `ORDER BY`, and frame clauses. The child window can then add or override clauses on top. + +```questdb-sql title="Inheriting from a base window" demo +SELECT + timestamp, + symbol, + price, + avg(price) OVER w1 AS symbol_avg, + avg(price) OVER w2 AS moving_avg +FROM trades +WHERE timestamp IN '[$today]' AND symbol = 'BTC-USDT' +WINDOW + w1 AS (ORDER BY timestamp), + w2 AS (w1 ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) +LIMIT 100; +``` + +Here `w2` inherits `ORDER BY timestamp` from `w1` and adds a frame clause. + +Chained inheritance is supported — a window can inherit from a window that itself inherits from another: + +```questdb-sql title="Chained inheritance" demo +SELECT + timestamp, + symbol, + price, + avg(price) OVER w3 AS moving_avg +FROM trades +WHERE timestamp IN '[$today]' +WINDOW + w1 AS (PARTITION BY symbol), + w2 AS (w1 ORDER BY timestamp), + w3 AS (w2 ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) +LIMIT 100; +``` + +**Merge rules** follow the SQL standard: + +| Clause | Behavior | +| --- | --- | +| `PARTITION BY` | Always inherited from the base. The child **cannot** specify its own. | +| `ORDER BY` | Child's takes precedence if specified, otherwise inherited from the base. | +| Frame | Child's takes precedence if specified, otherwise inherited from the base. | + +**Restrictions:** + +- The base window must be defined **earlier** in the `WINDOW` clause (no forward references). +- A child window that references a base **cannot** include its own `PARTITION BY`. +- Circular and self-references are not allowed. + +## Performance considerations + +- **ROWS frames** typically perform better than RANGE frames for large datasets +- **Partitioning** improves performance by processing smaller data chunks +- Consider **index usage** when ordering by timestamp columns +- Narrow windows process less data than wide windows + ## Common pitfalls ### Window functions in WHERE