QuestDB 9.3.2: TICK, arg_max, and Exponential Moving Averages
QuestDB 9.3.2 is here - and TICK is the headline. It is a new temporal interval syntax that turns complex time-range filters into one-liners. If you have ever wrestled with timezone-aware schedules, business day logic, or multi-session windows in SQL, this is for you.
Alongside TICK, we are shipping new aggregate and window functions for time-series analytics, a 6x speedup on Parquet queries, and improved LLM integration.
TICK: Temporal Interval Calendar Kit
TICK is a compact DSL for expressing complex time intervals directly in your WHERE clause. It replaces the UNION ALL chains, application-side date generation, and tangled BETWEEN logic that time-range queries typically require.
Here is the pitch in one example. Say you want a month of NYSE data - trading hours only, workdays only, in New York time:
SELECT * FROM tradesWHERE timestamp IN '2026-01-[01..31]T09:30@America/New_York#workday;6h30m';
That single string expands into 22 optimised interval scans, one per trading day, each using binary search on the designated timestamp. No unions. No subqueries. No client-side calendar math.
TICK syntax features
TICK supports a rich set of composable features:
- Bracket expansion:
[01..31],[09,14]:30 - Date variables:
$today,$yesterday,$tomorrow,$now - Business day arithmetic:
$today - 5bd - Timezone-aware DST handling:
@America/New_York - Day-of-week filters:
#workday,#Mon,Wed,Fri - Duration suffixes:
;6h30m
These are all composable in a single expression. A few more patterns to give you the flavour:
-- Last 5 business daysSELECT * FROM trades WHERE timestamp IN '$today - 5bd..$today - 1bd';-- Today's data (full day)SELECT * FROM fx_trades WHERE timestamp IN '$today';-- Last hour of dataSELECT * FROM fx_trades WHERE timestamp IN '$now - 1h..$now';-- Last 30 minutesSELECT * FROM fx_trades WHERE timestamp IN '$now - 30m..$now';
No other SQL database does this in a WHERE clause. We are excited to see what you build with it.
arg_min, arg_max, and geomean
You can already get the max price in a window. But which row produced it, and
when? arg_max gives you
correlated values from that row directly:
SELECT timestamp, symbol, max(price), arg_max(timestamp, price)FROM tradesWHERE timestamp IN '$today'SAMPLE BY 1h;
| timestamp | symbol | max(price) | arg_max(timestamp, price) |
|---|---|---|---|
| 2026-02-04T00:00:00.000000Z | ETH-USDT | 2256.94 | 2026-02-04T00:35:50.648999Z |
| 2026-02-04T00:00:00.000000Z | BTC-USDT | 76270.1 | 2026-02-04T00:42:29.675000Z |
| 2026-02-04T00:00:00.000000Z | SOL-USDT | 98.8 | 2026-02-04T00:42:20.482000Z |
| 2026-02-04T00:00:00.000000Z | ADA-USDT | 0.2957 | 2026-02-04T00:42:28.301000Z |
| 2026-02-04T00:00:00.000000Z | LTC-USDT | 60.1 | 2026-02-04T00:39:59.533999Z |
Now you get both the max price and the exact timestamp of the trade that hit it. No self-joins, no subqueries.
arg_min works the same way for
minimums. geomean returns the
geometric mean - better suited than arithmetic averages for compounding growth
rates and datasets with large outliers.
EMA, VWEMA, and percent_rank window functions
EMA and VWEMA are now native window functions. EMA applies exponential smoothing over a time period; VWEMA does the same but weights by volume, so high-activity periods carry more influence in the result.
SELECTsymbol,price,timestamp,avg(price, 'period', 10) OVER (PARTITION BY symbolORDER BY timestamp) AS ema_10FROM tradesWHERE timestamp IN '[$today]';
This query calculates a 10-period EMA for each symbol. The avg function with
'period', 10 applies exponential smoothing where recent prices have more
weight than older ones. Results are partitioned by symbol so each trading pair
gets its own independent EMA calculation, ordered chronologically.
percent_rank
returns the relative rank of a row within its partition. These execute inside
the query engine. No exporting data to Python or a spreadsheet for smoothing
calculations.
Performance highlights
Alongside new SQL features, QuestDB 9.3.2 delivers significant performance improvements:
- Parquet queries up to 6x faster through optimised row-group decoding
- Smarter interval scan pushdown:
dateaddpredicates,ORclauses over timestamps, and nested query offsets now all resolve to efficient interval scans - Reduced file mapping contention for high-concurrency query workloads
- Faster scans when
first/last/min/max(timestamp)appear inside other function calls
AI Assistant improvements
The AI assistant in the Web Console gets a significant upgrade in this release. Responses now stream in real-time, so you see answers as they are generated.
More importantly, the assistant can now investigate your table schemas automatically, looking up column types, indexes, and metadata to produce more accurate SQL suggestions.
You can also retry failed requests or cancel in-progress ones with a single click.
Learn more about AI-assisted workflows
Also in this release
bool_and(),bool_or(),bit_and(),bit_or(),bit_xor()aggregate functions- Geospatial functions:
within_box,within_radius,geo_within_radius_latlon- filter rows by bounding box or proximity
length_bytes()for varchar columns- Bugfixes for window joins, pgwire batching, and Parquet handling
QuestDB Enterprise 3.2.1: Scheduled Incremental Backups
QuestDB Enterprise 3.2.1 ships alongside this release, adding incremental backups to object storage with built-in scheduling.
Initiate a backup with BACKUP DATABASE,
or configure a cron schedule and retention policy, and backups run automatically.
Only changed data uploads, keeping them fast and bandwidth-efficient.
Monitor progress with backups(), or
stop a running backup with BACKUP ABORT.
Read the Backups documentation
Overhauled documentation
We have given our documentation a significant refresh. Whether you are getting started or diving deep into TICK syntax, the docs are cleaner, more comprehensive, and easier to navigate.
That is QuestDB 9.3.2. TICK changes how you express time-range filters, while new aggregation and window functions expand your analytics toolkit. The engine keeps getting faster underneath.
Read the full 9.3.2 release notes
Ready to try it?
docker pull questdb/questdbdocker run -p 9000:9000 -p 8812:8812 questdb/questdb
Try a TICK query first. We think you will be hooked.
Join our Slack or Discourse communities to share feedback and results. We are excited to see what you build with TICK.