From 405947fa098a46496c12327e451a6560f2128a88 Mon Sep 17 00:00:00 2001 From: javier Date: Thu, 29 Jan 2026 19:28:40 +0100 Subject: [PATCH] Update cookbook recipes to use TICK syntax for date filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace legacy dateadd(), today(), yesterday(), now() patterns with the new TICK syntax across 27 cookbook recipes: - timestamp IN today() → timestamp IN '$today' - timestamp IN yesterday() → timestamp IN '$yesterday' - timestamp > dateadd('d', -1, now()) → timestamp IN '$now - 1d..$now' - @lookback := dateadd(...) → @lookback := '$now - Xd..$now' Also simplifies VWAP query to use direct window function division. Adds new elapsed-time recipe in time-series patterns. --- documentation/cookbook/demo-data-schema.md | 6 +- .../integrations/grafana/variable-dropdown.md | 2 +- .../sql/advanced/conditional-aggregates.md | 2 +- .../advanced/consistent-histogram-buckets.md | 10 ++-- .../cookbook/sql/advanced/local-min-max.md | 4 +- .../sql/advanced/pivot-with-others.md | 4 +- .../advanced/rows-before-after-value-match.md | 2 +- .../cookbook/sql/advanced/sankey-funnel.md | 2 +- .../sql/advanced/top-n-plus-others.md | 6 +- .../cookbook/sql/advanced/unpivot-table.md | 4 +- .../sql/finance/aggressor-volume-imbalance.md | 2 +- .../cookbook/sql/finance/bollinger-bands.md | 4 +- .../sql/finance/bollinger-bandwidth.md | 10 ++-- .../sql/finance/liquidity-comparison.md | 6 +- documentation/cookbook/sql/finance/ohlc.md | 2 +- .../cookbook/sql/finance/rolling-stddev.md | 2 +- .../cookbook/sql/finance/tick-trin.md | 6 +- .../cookbook/sql/finance/volume-profile.md | 4 +- .../cookbook/sql/finance/volume-spike.md | 5 +- documentation/cookbook/sql/finance/vwap.md | 51 ++++++++--------- .../cookbook/sql/time-series/elapsed-time.md | 56 +++++++++++++++++++ .../sql/time-series/fill-from-one-column.md | 8 +-- .../time-series/force-designated-timestamp.md | 8 +-- .../sql/time-series/latest-n-per-partition.md | 18 +++--- .../sql/time-series/remove-outliers.md | 6 +- .../time-series/sample-by-interval-bounds.md | 2 +- .../sql/time-series/session-windows.md | 4 +- 27 files changed, 142 insertions(+), 94 deletions(-) create mode 100644 documentation/cookbook/sql/time-series/elapsed-time.md diff --git a/documentation/cookbook/demo-data-schema.md b/documentation/cookbook/demo-data-schema.md index 5cb5d1dc3..f46159aec 100644 --- a/documentation/cookbook/demo-data-schema.md +++ b/documentation/cookbook/demo-data-schema.md @@ -62,7 +62,7 @@ The table tracks **30 currency pairs**: EURUSD, GBPUSD, USDJPY, USDCHF, AUDUSD, ```questdb-sql demo title="Recent core_price updates" SELECT * FROM core_price -WHERE timestamp IN today() +WHERE timestamp IN '$today' LIMIT -10; ``` @@ -116,7 +116,7 @@ SELECT timestamp, symbol, array_count(bids[1]) as bid_levels, array_count(asks[1]) as ask_levels FROM market_data -WHERE timestamp IN today() +WHERE timestamp IN '$today' LIMIT -5; ``` @@ -170,7 +170,7 @@ CREATE TABLE 'fx_trades' ( ```questdb-sql demo title="Recent FX trades" SELECT * FROM fx_trades -WHERE timestamp IN today() +WHERE timestamp IN '$today' LIMIT -10; ``` diff --git a/documentation/cookbook/integrations/grafana/variable-dropdown.md b/documentation/cookbook/integrations/grafana/variable-dropdown.md index 337caaccb..13897f3f9 100644 --- a/documentation/cookbook/integrations/grafana/variable-dropdown.md +++ b/documentation/cookbook/integrations/grafana/variable-dropdown.md @@ -193,7 +193,7 @@ The PostgreSQL data source recognizes `__text` and `__value` as special column n **Different filter conditions:** ```sql -- Filter by time range -WHERE timestamp IN yesterday() +WHERE timestamp IN '$yesterday' -- Filter by multiple criteria WHERE symbol LIKE '%USDT' AND price > 1000 diff --git a/documentation/cookbook/sql/advanced/conditional-aggregates.md b/documentation/cookbook/sql/advanced/conditional-aggregates.md index c59472f81..d4ea0ebe1 100644 --- a/documentation/cookbook/sql/advanced/conditional-aggregates.md +++ b/documentation/cookbook/sql/advanced/conditional-aggregates.md @@ -33,7 +33,7 @@ SELECT sum(CASE WHEN amount <= 1.0 THEN amount END) as small_trade_volume, sum(amount) as total_volume FROM trades -WHERE timestamp >= dateadd('d', -1, now()) +WHERE timestamp IN '$now - 1d..$now' AND symbol IN ('BTC-USDT', 'ETH-USDT') GROUP BY symbol; ``` diff --git a/documentation/cookbook/sql/advanced/consistent-histogram-buckets.md b/documentation/cookbook/sql/advanced/consistent-histogram-buckets.md index 5734868f4..02e551ca6 100644 --- a/documentation/cookbook/sql/advanced/consistent-histogram-buckets.md +++ b/documentation/cookbook/sql/advanced/consistent-histogram-buckets.md @@ -20,7 +20,7 @@ SELECT floor(amount / @bucket_size) * @bucket_size AS bucket, count(*) AS count FROM trades -WHERE symbol = 'BTC-USDT' AND timestamp IN today() +WHERE symbol = 'BTC-USDT' AND timestamp IN '$today' GROUP BY bucket ORDER BY bucket; ``` @@ -53,7 +53,7 @@ DECLARE @bucket_count := 50 WITH raw_data AS ( SELECT price, amount FROM trades - WHERE symbol = 'BTC-USDT' AND timestamp IN today() + WHERE symbol = 'BTC-USDT' AND timestamp IN '$today' ), bucket_size AS ( SELECT (max(price) - min(price)) / (@bucket_count - 1) AS bucket_size FROM raw_data @@ -83,7 +83,7 @@ SELECT FROM trades WHERE symbol = 'BTC-USDT' AND amount > 0.000001 -- optional. Just adding here for easier visualization - AND timestamp IN today() + AND timestamp IN '$today' GROUP BY bucket ORDER BY bucket; ``` @@ -104,7 +104,7 @@ SELECT END AS bucket, count(*) AS count FROM trades -WHERE symbol = 'BTC-USDT' AND timestamp IN today() +WHERE symbol = 'BTC-USDT' AND timestamp IN '$today' GROUP BY bucket; ``` @@ -119,7 +119,7 @@ SELECT floor(amount / @bucket_size) * @bucket_size AS bucket, count(*) AS count FROM trades -WHERE symbol = 'BTC-USDT' AND timestamp IN today() +WHERE symbol = 'BTC-USDT' AND timestamp IN '$today' SAMPLE BY 1h ORDER BY timestamp, bucket; ``` diff --git a/documentation/cookbook/sql/advanced/local-min-max.md b/documentation/cookbook/sql/advanced/local-min-max.md index 47e731648..eaf2d365e 100644 --- a/documentation/cookbook/sql/advanced/local-min-max.md +++ b/documentation/cookbook/sql/advanced/local-min-max.md @@ -19,7 +19,7 @@ SELECT timestamp, bid_price, min(bid_price) OVER (ORDER BY timestamp RANGE 1 second PRECEDING) AS min_price, max(bid_price) OVER (ORDER BY timestamp RANGE 1 second PRECEDING) AS max_price FROM core_price -WHERE timestamp >= dateadd('m', -1, now()) AND symbol = 'EURUSD'; +WHERE timestamp IN '$now - 1m..$now' AND symbol = 'EURUSD'; ``` This returns the minimum and maximum bid price from the 1 second preceding each row. @@ -35,7 +35,7 @@ SELECT p.timestamp, p.bid_price, FROM core_price p WINDOW JOIN core_price pp ON symbol RANGE BETWEEN 1 second PRECEDING AND 1 second FOLLOWING -WHERE p.timestamp >= dateadd('m', -1, now()) AND p.symbol = 'EURUSD'; +WHERE p.timestamp IN '$now - 1m..$now' AND p.symbol = 'EURUSD'; ``` This returns the minimum and maximum bid price from 1 second before to 1 second after each row. diff --git a/documentation/cookbook/sql/advanced/pivot-with-others.md b/documentation/cookbook/sql/advanced/pivot-with-others.md index 7d3b99f88..13de855c0 100644 --- a/documentation/cookbook/sql/advanced/pivot-with-others.md +++ b/documentation/cookbook/sql/advanced/pivot-with-others.md @@ -13,7 +13,7 @@ You want to pivot data so that specific symbols (like EURUSD, GBPUSD, USDJPY) be ```questdb-sql demo title="Aggregated data per symbol" SELECT timestamp, symbol, SUM(bid_volume) AS total_bid FROM core_price -WHERE timestamp IN today() +WHERE timestamp IN '$today' SAMPLE BY 1m LIMIT 20; ``` @@ -55,7 +55,7 @@ SELECT timestamp, SUM(CASE WHEN symbol NOT IN ('EURUSD', 'GBPUSD', 'USDJPY') THEN bid_volume END) AS OTHERS FROM core_price -WHERE timestamp IN today() +WHERE timestamp IN '$today' SAMPLE BY 1m LIMIT 5; ``` diff --git a/documentation/cookbook/sql/advanced/rows-before-after-value-match.md b/documentation/cookbook/sql/advanced/rows-before-after-value-match.md index bf36be392..e32862699 100644 --- a/documentation/cookbook/sql/advanced/rows-before-after-value-match.md +++ b/documentation/cookbook/sql/advanced/rows-before-after-value-match.md @@ -27,7 +27,7 @@ SELECT timestamp, bid_price, LEAD(bid_price, 4) OVER () AS next_4, LEAD(bid_price, 5) OVER () AS next_5 FROM core_price -WHERE timestamp >= dateadd('m', -1, now()) AND symbol = 'EURUSD'; +WHERE timestamp IN '$now - 1m..$now' AND symbol = 'EURUSD'; ``` ## How it works diff --git a/documentation/cookbook/sql/advanced/sankey-funnel.md b/documentation/cookbook/sql/advanced/sankey-funnel.md index c2bfa908a..98aa042f9 100644 --- a/documentation/cookbook/sql/advanced/sankey-funnel.md +++ b/documentation/cookbook/sql/advanced/sankey-funnel.md @@ -43,7 +43,7 @@ WITH PrevEvents AS ( timestamp, lag(timestamp) OVER (PARTITION BY visitor_id ORDER BY timestamp) AS prev_ts FROM - events WHERE timestamp > dateadd('d', -7, now()) + events WHERE timestamp IN '$now - 7d..$now' AND metric_name = 'page_view' ), VisitorSessions AS ( SELECT *, diff --git a/documentation/cookbook/sql/advanced/top-n-plus-others.md b/documentation/cookbook/sql/advanced/top-n-plus-others.md index 9ac7b0aeb..1f602f614 100644 --- a/documentation/cookbook/sql/advanced/top-n-plus-others.md +++ b/documentation/cookbook/sql/advanced/top-n-plus-others.md @@ -31,7 +31,7 @@ WITH totals AS ( symbol, count() as total FROM trades - WHERE timestamp >= dateadd('d', -1, now()) + WHERE timestamp IN '$now - 1d..$now' ), ranked AS ( SELECT @@ -160,7 +160,7 @@ Results in three groups: top 5 individual, ranks 6-10 combined, rest combined. WITH totals AS ( SELECT symbol, count() as total FROM trades - WHERE timestamp >= dateadd('d', -1, now()) + WHERE timestamp IN '$now - 1d..$now' ), ranked AS ( SELECT *, rank() OVER (ORDER BY total DESC) as ranking @@ -196,7 +196,7 @@ WITH totals AS ( side, count() as total FROM trades - WHERE timestamp >= dateadd('d', -1, now()) + WHERE timestamp IN '$now - 1d..$now' ), ranked AS ( SELECT diff --git a/documentation/cookbook/sql/advanced/unpivot-table.md b/documentation/cookbook/sql/advanced/unpivot-table.md index d047a3ffa..8415a9692 100644 --- a/documentation/cookbook/sql/advanced/unpivot-table.md +++ b/documentation/cookbook/sql/advanced/unpivot-table.md @@ -42,7 +42,7 @@ WITH pivoted AS ( CASE WHEN side = 'buy' THEN price END as buy, CASE WHEN side = 'sell' THEN price END as sell FROM trades - WHERE timestamp >= dateadd('m', -5, now()) + WHERE timestamp IN '$now - 5m..$now' AND symbol = 'ETH-USDT' ), unpivoted AS ( @@ -114,7 +114,7 @@ WITH sensor_data AS ( humidity, pressure FROM sensors - WHERE timestamp >= dateadd('h', -1, now()) + WHERE timestamp IN '$now - 1h..$now' ) SELECT timestamp, sensor_id, 'temperature' as metric, temperature as value FROM sensor_data WHERE temperature IS NOT NULL diff --git a/documentation/cookbook/sql/finance/aggressor-volume-imbalance.md b/documentation/cookbook/sql/finance/aggressor-volume-imbalance.md index 05cd8fd7f..12176502a 100644 --- a/documentation/cookbook/sql/finance/aggressor-volume-imbalance.md +++ b/documentation/cookbook/sql/finance/aggressor-volume-imbalance.md @@ -19,7 +19,7 @@ WITH volumes AS ( SUM(CASE WHEN side = 'buy' THEN amount ELSE 0 END) AS buy_volume, SUM(CASE WHEN side = 'sell' THEN amount ELSE 0 END) AS sell_volume FROM trades - WHERE timestamp IN yesterday() + WHERE timestamp IN '$yesterday' AND symbol IN ('ETH-USDT', 'BTC-USDT', 'ETH-BTC') ) SELECT diff --git a/documentation/cookbook/sql/finance/bollinger-bands.md b/documentation/cookbook/sql/finance/bollinger-bands.md index 76c1ceff0..a9198748d 100644 --- a/documentation/cookbook/sql/finance/bollinger-bands.md +++ b/documentation/cookbook/sql/finance/bollinger-bands.md @@ -30,7 +30,7 @@ WITH OHLC AS ( last(price) AS close, sum(quantity) AS volume FROM fx_trades - WHERE symbol = 'EURUSD' AND timestamp IN yesterday() + WHERE symbol = 'EURUSD' AND timestamp IN '$yesterday' SAMPLE BY 15m ), stats AS ( SELECT @@ -109,7 +109,7 @@ WITH OHLC AS ( sum(quantity) AS volume FROM fx_trades WHERE symbol IN ('EURUSD', 'GBPUSD') - AND timestamp IN yesterday() + AND timestamp IN '$yesterday' SAMPLE BY 15m ), stats AS ( SELECT diff --git a/documentation/cookbook/sql/finance/bollinger-bandwidth.md b/documentation/cookbook/sql/finance/bollinger-bandwidth.md index eb7e96e71..2dacba97d 100644 --- a/documentation/cookbook/sql/finance/bollinger-bandwidth.md +++ b/documentation/cookbook/sql/finance/bollinger-bandwidth.md @@ -25,8 +25,8 @@ When BandWidth drops to historically low levels, the bands are in a "squeeze". P ```questdb-sql demo title="Calculate Bollinger BandWidth with range position" DECLARE @symbol := 'BTC-USDT', - @history_start := dateadd('M', -6, now()), - @display_start := dateadd('M', -1, now()) + @history := '$now - 6M..$now', + @display := '$now - 1M..$now' WITH daily_ohlc AS ( SELECT @@ -38,7 +38,7 @@ WITH daily_ohlc AS ( last(close) AS close FROM trades_ohlc_15m WHERE symbol = @symbol - AND timestamp > @history_start + AND timestamp IN @history SAMPLE BY 1d ), bands AS ( @@ -102,11 +102,11 @@ SELECT round(bandwidth, 4) AS bandwidth, round((bandwidth - min_bw) / (max_bw - min_bw) * 100, 1) AS range_position FROM with_range -WHERE timestamp > @display_start +WHERE timestamp IN @display ORDER BY timestamp; ``` -The query first aggregates 15-minute candles into daily OHLC, then calculates standard 20-day Bollinger Bands. This matches the traditional approach where SMA20 represents roughly one month of trading. The 6-month lookback (`@history_start`) establishes the historical range, while `@display_start` limits output to the last month. Standard deviation uses the variance formula `sqrt(avg(x²) - avg(x)²)`. +The query first aggregates 15-minute candles into daily OHLC, then calculates standard 20-day Bollinger Bands. This matches the traditional approach where SMA20 represents roughly one month of trading. The 6-month lookback (`@history`) establishes the historical range, while `@display` limits output to the last month. Standard deviation uses the variance formula `sqrt(avg(x²) - avg(x)²)`. The `range_position` shows where current BandWidth falls within the 6-month range: 0% means at the historical minimum, 100% at the maximum. This works well for identifying squeeze conditions since you're comparing against historical extremes. diff --git a/documentation/cookbook/sql/finance/liquidity-comparison.md b/documentation/cookbook/sql/finance/liquidity-comparison.md index 282779e35..ba5d5c497 100644 --- a/documentation/cookbook/sql/finance/liquidity-comparison.md +++ b/documentation/cookbook/sql/finance/liquidity-comparison.md @@ -18,7 +18,7 @@ You have order book snapshots for multiple instruments and want to compare which WITH latest_books AS ( SELECT timestamp, symbol, bids, asks FROM market_data - WHERE timestamp IN today() + WHERE timestamp IN '$today' LATEST ON timestamp PARTITION BY symbol ) SELECT @@ -49,7 +49,7 @@ SELECT last((L2PRICE(100_000, asks[2], asks[1]) - L2PRICE(100_000, bids[2], bids[1])) / ((L2PRICE(100_000, asks[2], asks[1]) + L2PRICE(100_000, bids[2], bids[1])) / 2)) * 10_000 AS spread_bps FROM market_data -WHERE timestamp IN today() +WHERE timestamp IN '$today' AND symbol IN ('EURUSD', 'GBPUSD', 'USDJPY') SAMPLE BY 1h ORDER BY timestamp, symbol; @@ -63,7 +63,7 @@ See how execution costs scale with order size: WITH latest_books AS ( SELECT symbol, bids, asks FROM market_data - WHERE timestamp IN today() + WHERE timestamp IN '$today' LATEST ON timestamp PARTITION BY symbol ) SELECT diff --git a/documentation/cookbook/sql/finance/ohlc.md b/documentation/cookbook/sql/finance/ohlc.md index 68998c881..094ddd489 100644 --- a/documentation/cookbook/sql/finance/ohlc.md +++ b/documentation/cookbook/sql/finance/ohlc.md @@ -22,7 +22,7 @@ SELECT last(price) AS close, sum(quantity) AS total_volume FROM fx_trades -WHERE timestamp IN today() +WHERE timestamp IN '$today' SAMPLE BY 1m; ``` diff --git a/documentation/cookbook/sql/finance/rolling-stddev.md b/documentation/cookbook/sql/finance/rolling-stddev.md index 1248f01e5..cf6c95edf 100644 --- a/documentation/cookbook/sql/finance/rolling-stddev.md +++ b/documentation/cookbook/sql/finance/rolling-stddev.md @@ -25,7 +25,7 @@ WITH stats AS ( AVG(price) OVER (PARTITION BY symbol ORDER BY timestamp) AS rolling_avg, AVG(price * price) OVER (PARTITION BY symbol ORDER BY timestamp) AS rolling_avg_sq FROM fx_trades - WHERE timestamp IN yesterday() AND symbol = 'EURUSD' + WHERE timestamp IN '$yesterday' AND symbol = 'EURUSD' ) SELECT timestamp, diff --git a/documentation/cookbook/sql/finance/tick-trin.md b/documentation/cookbook/sql/finance/tick-trin.md index 437ccb54d..694b94133 100644 --- a/documentation/cookbook/sql/finance/tick-trin.md +++ b/documentation/cookbook/sql/finance/tick-trin.md @@ -22,7 +22,7 @@ WITH with_previous AS ( SELECT timestamp, symbol, price, LAG(price) OVER (PARTITION BY symbol ORDER BY timestamp) AS prev_price FROM fx_trades - WHERE timestamp IN today() + WHERE timestamp IN '$today' ), classified AS ( SELECT symbol, @@ -64,7 +64,7 @@ WITH daily_stats AS ( last(price) AS current_price, sum(quantity) AS total_volume FROM fx_trades - WHERE timestamp IN today() + WHERE timestamp IN '$today' SAMPLE BY 1d ), classified AS ( @@ -94,7 +94,7 @@ WITH candles AS ( last(price) AS close_price, sum(quantity) AS total_volume FROM fx_trades - WHERE timestamp IN today() + WHERE timestamp IN '$today' SAMPLE BY 5m ), with_previous AS ( diff --git a/documentation/cookbook/sql/finance/volume-profile.md b/documentation/cookbook/sql/finance/volume-profile.md index 5ce2d184d..cd5adfea6 100644 --- a/documentation/cookbook/sql/finance/volume-profile.md +++ b/documentation/cookbook/sql/finance/volume-profile.md @@ -17,7 +17,7 @@ SELECT round(SUM(quantity), 2) AS volume FROM fx_trades WHERE symbol = 'EURUSD' - AND timestamp IN today() + AND timestamp IN '$today' ORDER BY price_bin; ``` @@ -31,7 +31,7 @@ For consistent histograms across different price ranges, calculate the tick size WITH raw_data AS ( SELECT price, quantity FROM fx_trades - WHERE symbol = 'EURUSD' AND timestamp IN today() + WHERE symbol = 'EURUSD' AND timestamp IN '$today' ), tick_size AS ( SELECT (max(price) - min(price)) / 49 as tick_size diff --git a/documentation/cookbook/sql/finance/volume-spike.md b/documentation/cookbook/sql/finance/volume-spike.md index d1f860c1d..736a326fc 100644 --- a/documentation/cookbook/sql/finance/volume-spike.md +++ b/documentation/cookbook/sql/finance/volume-spike.md @@ -16,8 +16,7 @@ Use the `LAG` window function to retrieve the previous candle's volume, then com ```questdb-sql demo title="Detect volume spikes exceeding 2x previous volume" DECLARE - @anchor_date := timestamp_floor('30s', now()), - @start_date := dateadd('h', -7, @anchor_date), + @range := '$now - 7h..$now', @symbol := 'EURUSD' WITH candles AS ( SELECT @@ -25,7 +24,7 @@ WITH candles AS ( symbol, sum(quantity) AS volume FROM fx_trades - WHERE timestamp >= @start_date + WHERE timestamp IN @range AND symbol = @symbol SAMPLE BY 30s ), diff --git a/documentation/cookbook/sql/finance/vwap.md b/documentation/cookbook/sql/finance/vwap.md index b92031149..4f3e6776f 100644 --- a/documentation/cookbook/sql/finance/vwap.md +++ b/documentation/cookbook/sql/finance/vwap.md @@ -28,30 +28,27 @@ WITH sampled AS ( total_volume, ((high + low + close) / 3) * total_volume AS traded_value FROM fx_trades_ohlc_1m - WHERE timestamp IN yesterday() AND symbol = 'EURUSD' -), -cumulative AS ( - SELECT - timestamp, symbol, - SUM(traded_value) OVER (ORDER BY timestamp) AS cumulative_value, - SUM(total_volume) OVER (ORDER BY timestamp) AS cumulative_volume - FROM sampled + WHERE timestamp IN '$yesterday' AND symbol = 'EURUSD' ) -SELECT timestamp, symbol, cumulative_value / cumulative_volume AS vwap -FROM cumulative; +SELECT + timestamp, symbol, + SUM(traded_value) OVER (ORDER BY timestamp) / + SUM(total_volume) OVER (ORDER BY timestamp) AS vwap +FROM sampled; ``` This query: 1. Reads 1-minute OHLC candles and calculates typical price × volume for each candle -2. Uses window functions to compute running totals of both traded value and volume -3. Divides cumulative traded value by cumulative volume to get VWAP at each timestamp +2. Divides cumulative traded value by cumulative volume using window functions ## How it works -The key insight is using `SUM(...) OVER (ORDER BY timestamp)` to create running totals: -- `cumulative_value`: Running sum of (typical price × volume) from market open -- `cumulative_volume`: Running sum of volume from market open -- Final VWAP: Dividing these cumulative values gives the volume-weighted average at each point +The key insight is using `SUM(...) OVER (ORDER BY timestamp)` 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 +``` 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. @@ -66,33 +63,29 @@ WITH sampled AS ( total_volume, ((high + low + close) / 3) * total_volume AS traded_value FROM fx_trades_ohlc_1m - WHERE timestamp IN yesterday() + WHERE timestamp IN '$yesterday' AND symbol IN ('EURUSD', 'GBPUSD', 'USDJPY') -), -cumulative AS ( - SELECT - timestamp, symbol, - SUM(traded_value) OVER (PARTITION BY symbol ORDER BY timestamp) AS cumulative_value, - SUM(total_volume) OVER (PARTITION BY symbol ORDER BY timestamp) AS cumulative_volume - FROM sampled ) -SELECT timestamp, symbol, cumulative_value / cumulative_volume AS vwap -FROM cumulative; +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; ``` -The `PARTITION BY symbol` ensures each symbol's VWAP is calculated independently, resetting the cumulative sums for each symbol. +The `PARTITION BY symbol` ensures each symbol's VWAP is calculated independently. ## Different time ranges ```sql -- Current trading day -WHERE timestamp IN today() +WHERE timestamp IN '$today' -- Specific date WHERE timestamp IN '2026-01-12' -- Last hour -WHERE timestamp >= dateadd('h', -1, now()) +WHERE timestamp IN '$now - 1h..$now' ``` :::tip Trading use cases diff --git a/documentation/cookbook/sql/time-series/elapsed-time.md b/documentation/cookbook/sql/time-series/elapsed-time.md new file mode 100644 index 000000000..71751defd --- /dev/null +++ b/documentation/cookbook/sql/time-series/elapsed-time.md @@ -0,0 +1,56 @@ +--- +title: Elapsed time between rows +sidebar_label: Elapsed time +description: Calculate the time elapsed between consecutive rows using lag() and datediff() +--- + +Calculate the time gap between consecutive events. Useful for detecting delays, measuring inter-arrival times, or identifying gaps in data streams. + +## Problem + +You want to know how much time passed between each row and the previous one, for example to spot gaps in a data feed or measure event frequency. + +## Solution + +```questdb-sql demo title="Elapsed time between consecutive trades" +SELECT + timestamp, + lag(timestamp) OVER (ORDER BY timestamp) AS prev_timestamp, + datediff('T', timestamp, lag(timestamp) OVER (ORDER BY timestamp)) AS elapsed_millis +FROM trades +WHERE symbol = 'BTC-USDT' + AND timestamp IN '$today' +LIMIT 20; +``` + +The `datediff('T', timestamp, prev_timestamp)` function returns the difference in milliseconds. Change the unit to control precision: + +| Unit | Description | +|------|-------------| +| `'s'` | Seconds | +| `'T'` | Milliseconds | +| `'U'` | Microseconds | + +## Raw timestamp subtraction + +If you subtract timestamps directly instead of using `datediff`, the result is in the **native resolution of the column** (microseconds for `TIMESTAMP`, nanoseconds for `TIMESTAMP_NS`): + +```questdb-sql demo title="Raw difference in microseconds" +SELECT + timestamp, + timestamp - lag(timestamp) OVER (ORDER BY timestamp) AS elapsed_micros +FROM trades +WHERE symbol = 'BTC-USDT' + AND timestamp IN '$today' +LIMIT 20; +``` + +:::note TIMESTAMP vs TIMESTAMP_NS +The `trades` table uses `TIMESTAMP` (microsecond precision), so subtraction gives microseconds. Tables like `fx_trades` use `TIMESTAMP_NS` (nanosecond precision), where subtraction gives nanoseconds. +::: + +:::info Related documentation +- [lag() function](/docs/query/functions/window-functions/reference/#lag) +- [datediff() function](/docs/query/functions/date-time/#datediff) +- [Designated timestamp](/docs/concepts/designated-timestamp/) +::: diff --git a/documentation/cookbook/sql/time-series/fill-from-one-column.md b/documentation/cookbook/sql/time-series/fill-from-one-column.md index 7d4997bdf..b7b97a9a6 100644 --- a/documentation/cookbook/sql/time-series/fill-from-one-column.md +++ b/documentation/cookbook/sql/time-series/fill-from-one-column.md @@ -13,7 +13,7 @@ You have a query like this: ```questdb-sql demo title="SAMPLE BY with FILL(PREV)" SELECT timestamp, symbol, avg(bid_price) as bid_price, avg(ask_price) as ask_price FROM core_price -WHERE symbol = 'EURUSD' AND timestamp IN today() +WHERE symbol = 'EURUSD' AND timestamp IN '$today' SAMPLE BY 100T FILL(PREV, PREV); ``` @@ -22,7 +22,7 @@ But when there is an interpolation, instead of getting the PREV value for `bid_p ```sql SELECT timestamp, symbol, avg(bid_price) as bid_price, avg(ask_price) as ask_price FROM core_price -WHERE symbol = 'EURUSD' AND timestamp IN today() +WHERE symbol = 'EURUSD' AND timestamp IN '$today' SAMPLE BY 100T FILL(PREV(ask_price), PREV); ``` @@ -34,7 +34,7 @@ The only way to do this is in multiple steps within a single query: first get th WITH sampled AS ( SELECT timestamp, symbol, avg(bid_price) as bid_price, avg(ask_price) as ask_price FROM core_price - WHERE symbol = 'EURUSD' AND timestamp IN today() + WHERE symbol = 'EURUSD' AND timestamp IN '$today' SAMPLE BY 100T FILL(null) ), with_previous_vals AS ( SELECT *, @@ -54,7 +54,7 @@ You can mark which rows were filled by adding a column that flags interpolated v WITH sampled AS ( SELECT timestamp, symbol, avg(bid_price) as bid_price, avg(ask_price) as ask_price FROM core_price - WHERE symbol = 'EURUSD' AND timestamp IN today() + WHERE symbol = 'EURUSD' AND timestamp IN '$today' SAMPLE BY 100T FILL(null) ), with_previous_vals AS ( SELECT *, diff --git a/documentation/cookbook/sql/time-series/force-designated-timestamp.md b/documentation/cookbook/sql/time-series/force-designated-timestamp.md index da49887ab..bd7d7cec6 100644 --- a/documentation/cookbook/sql/time-series/force-designated-timestamp.md +++ b/documentation/cookbook/sql/time-series/force-designated-timestamp.md @@ -18,7 +18,7 @@ SELECT bid_price FROM core_price -WHERE timestamp IN today() +WHERE timestamp IN '$today' LIMIT 10; ``` @@ -38,7 +38,7 @@ WITH t AS ( bid_price FROM core_price - WHERE timestamp >= dateadd('h', -1, now()) + WHERE timestamp IN '$now - 1h..$now' ORDER BY time ) TIMESTAMP (time) ) @@ -57,9 +57,9 @@ You can restore the designated timestamp by applying `ORDER BY` and then using ` ( SELECT * FROM ( - SELECT timestamp, symbol FROM core_price WHERE timestamp >= dateadd('m', -1, now()) + SELECT timestamp, symbol FROM core_price WHERE timestamp IN '$now - 1m..$now' UNION ALL - SELECT timestamp, symbol FROM core_price WHERE timestamp >= dateadd('m', -1, now()) + SELECT timestamp, symbol FROM core_price WHERE timestamp IN '$now - 1m..$now' ) ORDER BY timestamp ) TIMESTAMP(timestamp) diff --git a/documentation/cookbook/sql/time-series/latest-n-per-partition.md b/documentation/cookbook/sql/time-series/latest-n-per-partition.md index 13be6ca66..1090a8db0 100644 --- a/documentation/cookbook/sql/time-series/latest-n-per-partition.md +++ b/documentation/cookbook/sql/time-series/latest-n-per-partition.md @@ -17,7 +17,7 @@ You want to get the latest N rows for each distinct value in a column. For examp ```questdb-sql demo title="LATEST ON returns only 1 row per symbol" SELECT * FROM trades -WHERE timestamp in today() +WHERE timestamp IN '$today' LATEST ON timestamp PARTITION BY symbol; ``` @@ -33,7 +33,7 @@ WITH ranked AS ( *, row_number() OVER (PARTITION BY symbol ORDER BY timestamp DESC) as rn FROM trades - WHERE timestamp in today() + WHERE timestamp IN '$today' ) SELECT timestamp, symbol, side, price, amount FROM ranked @@ -104,7 +104,7 @@ DECLARE @limit := 10 WITH ranked AS ( SELECT *, row_number() OVER (PARTITION BY symbol ORDER BY timestamp DESC) as rn FROM trades - WHERE timestamp >= dateadd('d', -1, now()) + WHERE timestamp IN '$now - 1d..$now' ) SELECT * FROM ranked WHERE rn <= @limit; ``` @@ -116,7 +116,7 @@ WITH ranked AS ( *, row_number() OVER (PARTITION BY symbol ORDER BY timestamp DESC) as rn FROM trades - WHERE timestamp in today() + WHERE timestamp IN '$today' AND side = 'buy' -- Additional filter before ranking ) SELECT timestamp, symbol, side, price, amount @@ -129,7 +129,7 @@ WHERE rn <= 5; WITH ranked AS ( SELECT *, row_number() OVER (PARTITION BY symbol ORDER BY timestamp DESC) as rn FROM trades - WHERE timestamp in today() + WHERE timestamp IN '$today' ) SELECT timestamp, symbol, price, rn as rank FROM ranked @@ -165,7 +165,7 @@ LIMIT -100; WITH ranked AS ( SELECT *, row_number() OVER (PARTITION BY symbol ORDER BY timestamp DESC) as rn FROM trades - WHERE timestamp in today() -- Filter early + WHERE timestamp IN '$today' -- Filter early ) SELECT * FROM ranked WHERE rn <= 5; @@ -174,13 +174,13 @@ WITH ranked AS ( SELECT *, row_number() OVER (PARTITION BY symbol ORDER BY timestamp DESC) as rn FROM trades -- No filter ) -SELECT * FROM ranked WHERE rn <= 5 AND timestamp in today(); +SELECT * FROM ranked WHERE rn <= 5 AND timestamp IN '$today'; ``` **Limit partitions:** ```sql -- Process only specific symbols -WHERE timestamp in today() +WHERE timestamp IN '$today' AND symbol IN ('BTC-USDT', 'ETH-USDT', 'SOL-USDT') ``` @@ -196,7 +196,7 @@ WITH ranked AS ( price, row_number() OVER (PARTITION BY symbol ORDER BY timestamp DESC) as rn FROM trades - WHERE timestamp in today() + WHERE timestamp IN '$today' ) SELECT symbol, diff --git a/documentation/cookbook/sql/time-series/remove-outliers.md b/documentation/cookbook/sql/time-series/remove-outliers.md index c7b2c84c1..f1d1c235c 100644 --- a/documentation/cookbook/sql/time-series/remove-outliers.md +++ b/documentation/cookbook/sql/time-series/remove-outliers.md @@ -21,7 +21,7 @@ SELECT max(price), sum(quantity) AS volume FROM fx_trades -WHERE symbol = 'EURUSD' AND timestamp > dateadd('d', -14, now()) +WHERE symbol = 'EURUSD' AND timestamp IN '$now - 14d..$now' SAMPLE BY 1d; ``` @@ -42,7 +42,7 @@ WITH moving_trades AS ( RANGE BETWEEN 7 days PRECEDING AND 1 day PRECEDING ) AS moving_avg_price FROM fx_trades - WHERE symbol = 'EURUSD' AND timestamp > dateadd('d', -21, now()) + WHERE symbol = 'EURUSD' AND timestamp IN '$now - 21d..$now' ) SELECT timestamp, symbol, @@ -52,7 +52,7 @@ SELECT max(price), sum(quantity) AS volume FROM moving_trades -WHERE timestamp > dateadd('d', -14, now()) +WHERE timestamp IN '$now - 14d..$now' AND moving_avg_price IS NOT NULL AND ABS(price - moving_avg_price) <= moving_avg_price * 0.01 SAMPLE BY 1d; diff --git a/documentation/cookbook/sql/time-series/sample-by-interval-bounds.md b/documentation/cookbook/sql/time-series/sample-by-interval-bounds.md index 5f76d7067..6820fd781 100644 --- a/documentation/cookbook/sql/time-series/sample-by-interval-bounds.md +++ b/documentation/cookbook/sql/time-series/sample-by-interval-bounds.md @@ -25,7 +25,7 @@ SELECT max(price), sum(quantity) AS volume FROM fx_trades -WHERE symbol = 'EURUSD' AND timestamp IN today() +WHERE symbol = 'EURUSD' AND timestamp IN '$today' SAMPLE BY 15m; ``` diff --git a/documentation/cookbook/sql/time-series/session-windows.md b/documentation/cookbook/sql/time-series/session-windows.md index c819cc6dd..f39c8c2d6 100644 --- a/documentation/cookbook/sql/time-series/session-windows.md +++ b/documentation/cookbook/sql/time-series/session-windows.md @@ -41,7 +41,7 @@ WITH prevEvents AS ( lag(lock_status::int) -- lag doesn't support booleans, so we convert to 1 or 0 OVER (PARTITION BY vehicle_id ORDER BY timestamp) as prev_status FROM vehicle_events - WHERE timestamp IN today() + WHERE timestamp IN '$today' ), ride_sessions AS ( SELECT *, @@ -154,7 +154,7 @@ WITH prevEvents AS ( lag(lock_status::int) -- lag doesn't support booleans, so we convert to 1 or 0 OVER (PARTITION BY vehicle_id ORDER BY timestamp) as prev_status FROM vehicle_events - WHERE timestamp >= dateadd('M', -3, now()) + WHERE timestamp IN '$now - 3M..$now' ), ride_sessions AS ( SELECT *,