Download Latest Version questdb-9.3.3-rt-linux-x86-64.tar.gz (91.2 MB)
Email in envelope

Get an email when there's a new version of QuestDB

Home / 9.3.2
Name Modified Size InfoDownloads / Week
Parent folder
questdb-9.3.2-no-jre-bin.tar.gz 2026-01-28 31.0 MB
questdb-9.3.2-rt-linux-x86-64.tar.gz 2026-01-28 87.4 MB
questdb-9.3.2-rt-windows-x86-64.tar.gz 2026-01-28 78.4 MB
9.3.2 source code.tar.gz 2026-01-28 59.5 MB
9.3.2 source code.zip 2026-01-28 65.0 MB
README.md 2026-01-28 11.3 kB
Totals: 6 Items   321.3 MB 1

QuestDB 9.3.2

QuestDB 9.3.2 continues the trend of performance upgrades and bugfixes, with some additional new features. Importantly, we introduce the new TICK syntax, a compact DSL for expressing time intervals, alongside faster aggregations, improved applicability of interval scans, and fast parquet queries.

For any questions or feedback, please join us on Slack or on Discourse.

See also our prettier release notes page.

Highlights

New TICK syntax

The Temporal Interval Calendar Kit (TICK is a new DSL for expressing complex time ranges and intervals in a compact, easy-to-use format.

For example, let's say that you want to query one month of data from NYSE, only including trading days and hours. The data in the database is stored in UTC format, so you'd need to convert to different time zones, and build a very complex WHERE clause with ANDs and ORs. Or, alternatively, send lots of narrow queries and combine the results.

Instead, you can express this in a simple string:

:::sql
-- NYSE trading hours on workdays for January
SELECT * FROM trades
WHERE ts IN '2024-01-[01..31]T09:30@America/New_York#workday;6h29m';

Reading this from left to right, we will the days in 2024-01 in the range of 01..31 (all of them), with the interval beginning at 09:30 in New York time, only considering working days, and the interval ending after 6h29m on each day (inclusive, so at 16:00 New York time).

We then compile this to an efficient interval scan, with the time filtering pushed down:

 intervals: [
    ("2024-01-01T14:30:00.000000Z","2024-01-01T20:59:59.999999Z"),

    ("2024-01-02T14:30:00.000000Z","2024-01-02T20:59:59.999999Z"),

    ("2024-01-03T14:30:00.000000Z","2024-01-03T20:59:59.999999Z"),

    ...
]

This syntax makes it easier to construct complex intervals, and keep confidence that the execution plan will be optimal.

Please see the TICK docs for more information.

arg_min, arg_max, and geomean aggregates

Let's say you are monitoring trading data, and you track what the max trading price was over an hourly period. You can express that like this:

:::sql
SELECT timestamp, symbol, max(price)
FROM trades
WHERE timestamp IN today()
AND symbol IN ('BTC-USDT', 'ETH-USDT')
SAMPLE BY 1h;
timestamp symbol max(price)
2026-01-28T00:00:00.000000Z ETH-USDT 3029.32
2026-01-28T00:00:00.000000Z BTC-USDT 89488.3
2026-01-28T01:00:00.000000Z BTC-USDT 89495.0
2026-01-28T01:00:00.000000Z ETH-USDT 3026.58
2026-01-28T02:00:00.000000Z BTC-USDT 89450.0
2026-01-28T02:00:00.000000Z ETH-USDT 3018.31
... ... ...

But now we have a problem - at what time or trade was that the max price?

Using arg_max, we can extract values from the row where the price was max:

:::sql
SELECT timestamp, symbol, max(price), arg_max(timestamp, price)
FROM trades
WHERE timestamp IN today()
AND symbol IN ('BTC-USDT', 'ETH-USDT')
SAMPLE BY 1h;
timestamp symbol max(price) arg_max(timestamp, price)
2026-01-28T00:00:00.000000Z ETH-USDT 3029.32 2026-01-28T00:26:05.512000Z
2026-01-28T00:00:00.000000Z BTC-USDT 89488.3 2026-01-28T00:59:57.971000Z
2026-01-28T01:00:00.000000Z BTC-USDT 89495.0 2026-01-28T01:20:45.782000Z
2026-01-28T01:00:00.000000Z ETH-USDT 3026.58 2026-01-28T01:03:35.940000Z
2026-01-28T02:00:00.000000Z BTC-USDT 89450.0 2026-01-28T02:05:50.368000Z
2026-01-28T02:00:00.000000Z ETH-USDT 3018.31 2026-01-28T02:02:04.936999Z
... ... ...

Pretty handy!

geomean(D) calculates the geometric mean for a set of positive numbers; this is a useful average variant, which improves accuracy for data with large outliers, and commonly used for growth rate calculations.

For more details, please check out the aggregate function documentation.

EMA, VWEMA, percent_rank window functions

We've introduced new exponential moving average (EMA) window functions. This smooths out your data base on a smoothing and time period.

Additionally, we've added a volume-weighted variant (VWEMA), which is commonly used for trading data, allowing for smoothing of the function whilst still prioritising higher-volume trades.

Here's an example of the syntax:

:::sql
-- Standard average
avg(value) OVER (window_definition)

-- Exponential Moving Average (EMA)
avg(value, kind, param) OVER (window_definition)

-- Volume-Weighted Exponential Moving Average (VWEMA)
avg(value, kind, param, volume) OVER (window_definition)

percent_rank returns the relative rank of a row within a group of values. This is calculated based on the number of rows within the group, and its position.

For more information, please see the window functions documentation.

Performance

Time-intrinsics upgrades

  • QuestDB will now optimise time-range queries over tables where the range uses a constant dateadd offset.
    • e.g. WHERE dateadd('m', 15, timestamp) = '2022-03-08T18:30:00Z'
  • QuestDB will optimise time filters that include OR clauses:
    • e.g. WHERE timestamp IN '2018-01-01' OR timestamp IN '2018-01-02' OR timestamp IN '2018-01-03'
  • We've improved how time-range pushdown is handled within nested queries:
    • e.g. SELECT * FROM (SELECT dateadd('h', -1, timestamp) as ts, price FROM trades) WHERE ts in '2022'

Parquet and File-handling

  • We have significantly sped-up querying parquet queries by optimising how row-groups are decoded, giving up to a 6x performance boost versus the last release.
  • We've optimised how file mappings are handled, removing sporadic query latency increases over extremely concurrent query workloads.

Changelist

New Contributors

Full Changelog: https://github.com/questdb/questdb/compare/9.3.1...9.3.2

Source: README.md, updated 2026-01-28