QuestDB Time-Series SQL: Mastering SAMPLE BY, LATEST ON, and ASOF JOIN (Hands-On Guide)
Meta description (156 chars):
Hands-on QuestDB time-series SQL: use SAMPLE BY for rollups, LATEST ON for last-known values, and ASOF JOIN for nearest time merges—real patterns that ship.
Why this matters (a quick story)
You’ve got billions of events flowing in every day—prices, sensor readings, app metrics. Your team needs rollups, last-known values, and near-time joins to power dashboards and anomaly detectors. QuestDB’s time-series SQL gives you all three with SAMPLE BY, LATEST ON, and ASOF JOIN—no custom pipeline glue or clunky windowing jobs required.
This guide shows how to use each feature correctly and fast, with production caveats mid-level data engineers actually run into.
The setup (minimal schema)
-- Partitioned table with a designated timestamp
CREATE TABLE trades (
ts TIMESTAMP,
symbol SYMBOL, -- use SYMBOL for high-cardinality tags
price DOUBLE,
size INT
) TIMESTAMP(ts)
PARTITION BY DAY;
TIMESTAMP(ts) elects the designated timestamp—the column QuestDB uses to time-order data and enable time-series features like SAMPLE BY, LATEST ON, and ASOF JOIN. (QuestDB)
SAMPLE BY: fast, calendar-aligned rollups
What it does
SAMPLE BY bins rows into fixed time buckets (seconds → years) and computes aggregates—ideal for dashboards and SLOs. Requires a designated timestamp. (QuestDB)
Common pattern
-- Per-minute rollups for today, with sensible gaps handling
SELECT
ts, -- the bucket timestamp
count() AS trades,
avg(price) AS avg_px
FROM trades
WHERE ts IN today()
SAMPLE BY 1m
FILL(0, PREV); -- fill count() with 0, avg() with previous value
Tips that save you time:
- Fill gaps using
FILL(NULL|PREV|LINEAR|<constant>). You can provide one fill per aggregate in SELECT order. (QuestDB) - Align to calendar by default; you can still control time zone and offsets when needed. (QuestDB)
- Need rows outside your data’s bounds (pre/post-fill)? Use
FROM '...' TO '...'withSAMPLE BYto explicitly shape the output range. (QuestDB)
Small mental model:
raw data ──●● ● ● ●───● (irregular)
SAMPLE BY 1m → |●|●|●|●|●|●|●| (regular bins + fill)
LATEST ON: last-known values per key
What it does
LATEST ON <timestamp> PARTITION BY <key(s)> returns the most recent row for each series (e.g., per symbol). This needs a designated timestamp (or a subquery that exposes one). (QuestDB)
Common patterns
Latest price per symbol:
SELECT symbol, price, ts
FROM trades
LATEST ON ts PARTITION BY symbol;
Latest in a time slice (note how parentheses change execution order):
-- WHERE first, then latest
SELECT * FROM trades
WHERE ts IN '2025-11-20'
LATEST ON ts PARTITION BY symbol;
-- LATEST first, then filter
(SELECT * FROM trades LATEST ON ts PARTITION BY symbol)
WHERE price > 100;
Execution order matters for result semantics and performance—use parentheses to make it explicit. (QuestDB)
ASOF JOIN: nearest-time merges
What it does
ASOF JOIN pairs each left-table row with the latest right-table row at the same or earlier timestamp (optionally within a tolerance). This is how you align trades to quotes, metrics to configs, or events to feature flags. (QuestDB)
Trades to quotes with a 2-second window:
SELECT
t.ts, t.symbol, t.price,
q.bid, q.ask
FROM trades t
ASOF JOIN quotes q ON (symbol)
TOLERANCE 2s;
Important details:
- By default, QuestDB uses each table’s designated timestamp for the join. (QuestDB)
- If you must join on a different time column, sort a subquery (
ORDER BY other_ts ASC) so that column becomes the subquery’s effective timestamp for the join. The engine will use it automatically. (QuestDB) - Use
TOLERANCEto bound the look-back window—it improves correctness and performance. (QuestDB)
Visual intuition (per key)
left (trades): -----t1-----------t2----------t3----
right (quotes): ---qA------qB-------------qC--------
ASOF result: t1→qA t2→qB t3→qC
When to use which? (cheat sheet)
| Clause | Solves for | Typical SELECT pattern | Gotchas / notes |
|---|---|---|---|
SAMPLE BY | Rollups & resampling | ... SAMPLE BY 1m FILL(0, PREV) | Needs designated timestamp; fill rules differ for keyed vs non-keyed queries; default aligns to calendar. (QuestDB) |
LATEST ON | Last-known value per series/key | ... LATEST ON ts PARTITION BY symbol | Execution order with WHERE changes semantics; multi-column partitions may scan more. (QuestDB) |
ASOF JOIN | Nearest-time merge across tables | ... ASOF JOIN b ON (key) TOLERANCE 2s | Uses designated timestamp by default; for other ts, ORDER BY in a subquery; add TOLERANCE. (QuestDB) |
Real example end-to-end
Goal: build a per-minute trade volume dashboard, overlay last price per symbol, and enrich with near-time quote spreads.
-- 1) Per-minute rollups (count + avg price)
WITH minute_rollups AS (
SELECT ts, count() AS trades, avg(price) AS avg_px
FROM trades
WHERE ts IN today()
SAMPLE BY 1m
FILL(0, PREV)
),
-- 2) Latest price per symbol (time slice)
latest_px AS (
(SELECT symbol, price, ts
FROM trades
WHERE ts IN today()
LATEST ON ts PARTITION BY symbol)
),
-- 3) Enrich rollups with quotes near each minute mark
rollups_with_spread AS (
SELECT
m.ts,
m.trades,
m.avg_px,
q.bid,
q.ask,
(q.ask - q.bid) AS spread
FROM minute_rollups m
ASOF JOIN quotes q TOLERANCE 2s ON () -- join by time only; or ON (symbol)
)
SELECT *
FROM rollups_with_spread
ORDER BY ts;
This pattern avoids custom stream jobs for last-value lookups and near-time alignment—pure SQL.
Best practices and common pitfalls
Schema & partitions
- Always set a designated timestamp; it unlocks time-series features and optimal scans. (QuestDB)
- Choose partitions that match retention and query windows (e.g.,
PARTITION BY DAY). You can add TTLs to drop old partitions automatically. (QuestDB)
SAMPLE BY
- Use
FILL(0, PREV, ...)to keep charts continuous; prefer constants for additive metrics (count()),PREVorLINEARfor ratios and averages. (QuestDB) - If you need rows before/after data exists (pre/post-fill ranges), use
FROM ... TO .... (QuestDB)
LATEST ON
- For unordered data or tables without a designated timestamp, wrap with a subquery; otherwise the engine can’t derive “latest.” (QuestDB)
- Be explicit about execution order with parentheses when mixing
WHEREandLATEST ON. (QuestDB)
ASOF JOIN
- Start with the default: rely on each table’s designated timestamp. If you must use another column,
ORDER BY <col> ASCin a subquery so QuestDB picks it up as the subquery timestamp. (QuestDB) - Add a
TOLERANCEwindow to avoid accidental joins to stale rows and to speed up lookups. (QuestDB)
Key references (official)
SAMPLE BYreference & fill behavior. (QuestDB)LATEST ONreference (syntax, execution order). (QuestDB)ASOF JOINreference (semantics, tolerance). (QuestDB)- Designated timestamp concept. (QuestDB)
CREATE TABLE(partitions, TTL, WAL default). (QuestDB)
Internal link ideas (official pages to link from this article)
- QuestDB
SAMPLE BYSQL reference – full syntax,FILL, alignment,FROM...TO. (QuestDB) LATEST ONreference – single vs multi-key, execution order examples. (QuestDB)ASOF JOINreference – default timestamp usage,TOLERANCE. (QuestDB)- Designated timestamp concept – why it matters, properties. (QuestDB)
JOINoverview – supported join types (ASOF,LT,SPLICE). (QuestDB)CREATE TABLE– partitions, TTL, dedup, WAL defaults. (QuestDB)- Time-series optimizations – engine behaviors worth knowing. (QuestDB)
- Materialized views – precompute rollups when queries get heavy. (QuestDB)
Summary & call-to-action
- Use
SAMPLE BYfor rollups and resampling (rememberFILL). - Use
LATEST ONfor last-known per-key values—watch execution order. - Use
ASOF JOINto align data across tables in time—addTOLERANCE.
Spin up a small dataset, run the snippets above, and then check the linked docs to deepen your understanding. Next step: add TTL and materialized views to keep query latency flat as data grows.
Image prompt (for your designer/AI tool)
“A clean, modern data architecture diagram showing QuestDB tables with a designated timestamp, a SAMPLE BY 1m rollup, a LATEST ON selection per symbol, and an ASOF JOIN into a quotes table — minimalistic, high contrast, 3D isometric style.”
Tags
#NoSQL #QuestDB #TimeSeriesSQL #DataEngineering #DatabaseDesign #Scalability #SQL #RealTimeAnalytics #Architecture










Leave a Reply