KDB knowledge

SAMPLE BY

Put rows into time buckets (1 minute, 15 minutes, 1 hour, 1 day, 1 week, 1 month) and make candles.

SAMPLE BY cuts time into equal pieces called buckets (for example 15 minutes) and makes one result row for each bucket. With first, max, min, last and sum this makes candles (open, high, low, close, volume).

Example: 15-minute CITY candles made from 1-minute candles (sample data: sample)

SELECT time,
       first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close,
       sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
SAMPLE BY 15m
LIMIT 4;

Result:

time (TIMESTAMP)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)
2026-09-10T05:15:00.000Z504.6505.3503.9504.413210
2026-09-10T05:30:00.000Z504.4506.8503.8506.511690
2026-09-10T05:45:00.000Z506.5507505.3506.411270
2026-09-10T06:00:00.000Z506.4506.6504.9505.310850

(4 rows)

SAMPLE BY

Syntax:

SELECT time_column, [key_column, ...] aggregate, ...
FROM table | view | (SELECT ...) alias
[WHERE condition]
SAMPLE BY interval
    [FROM start] [TO end]
    [FILL(NONE | NULL | PREV | LINEAR | constant [, ...])]
    [ALIGN TO CALENDAR | ALIGN TO FIRST OBSERVATION]
    [TIME ZONE 'zone']
    [WITH OFFSET '+HH:MM']
    [WEEK START SUNDAY | MONDAY]
[GROUP BY expression, ...]
[HAVING condition]
[ORDER BY ...] [LIMIT n [OFFSET m]]

Makes one row for each bucket of time (and for each key, like symbol). The options after the interval can come in any order, but each option only once.

PartMeaning
time_columnthe table's time column. In the result it holds the bucket start. You can leave it out of the SELECT list, or give it another name with AS.
key_columna plain column (not inside an aggregate), like symbol. It becomes a group key: one row per bucket per symbol (the implicit GROUP BY rule).
aggregatefirst, last, max, min, sum, avg, count, vwap, ... (aggregate functions)
intervalthe bucket size: 15m, 1h, 1d, 1w, 1M, ... (see intervals)

Example: Daily candles for every symbol, made from minute candles (sample data: sample)

SELECT time, symbol,
       first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close,
       sum(volume) AS volume, count(*) AS minutes
FROM ochl_minutes
SAMPLE BY 1d
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)minutes (LONG)
2026-09-09T00:00:00.000ZCITY512512.4504504.6182390240
2026-09-10T00:00:00.000ZCITY504.6507500.7504.9183040240
2026-09-09T00:00:00.000ZCOAST398398.8379.2380.1186330240
2026-09-10T00:00:00.000ZCOAST380.1382373.2376.1186200236

(4 rows)

Example: A window over the candles: yesterday's close next to today's (sample data: sample)

SELECT time, symbol, last(close) AS close,
       lag(last(close)) OVER (PARTITION BY symbol ORDER BY time) AS prev_close
FROM ochl_day
WHERE time IN '2026-09' AND time < '2026-09-04'
SAMPLE BY 1d
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)prev_close (DOUBLE)
2026-09-01T00:00:00.000ZCITY502.4NULL
2026-09-02T00:00:00.000ZCITY496.4502.4
2026-09-03T00:00:00.000ZCITY497.9496.4
2026-09-01T00:00:00.000ZCOAST367.4NULL
2026-09-02T00:00:00.000ZCOAST369.2367.4
2026-09-03T00:00:00.000ZCOAST373.2369.2
2026-09-01T00:00:00.000ZVOLT169NULL
2026-09-02T00:00:00.000ZVOLT171.5169
2026-09-03T00:00:00.000ZVOLT176171.5

(9 rows)

Example: Trade log: shares and turnover per day per symbol (sample data: sample)

SELECT date, symbol, sum(quantity) AS shares, round(sum(amount), 2) AS turnover, count(*) AS trades
FROM trade_log
WHERE symbol IN ('CITY', 'COAST')
SAMPLE BY 1d
ORDER BY symbol, date;

Result:

date (TIMESTAMP)symbol (SYMBOL)shares (LONG)turnover (DOUBLE)trades (LONG)
2026-09-09T00:00:00.000ZCITY15107774004
2026-09-10T00:00:00.000ZCITY9104698307
2026-09-09T00:00:00.000ZCOAST220881702
2026-09-10T00:00:00.000ZCOAST285011393409

(4 rows)

Example: Weekly volume, only the big weeks (HAVING) (sample data: sample)

SELECT time, symbol, sum(volume) AS volume
FROM ochl_day
WHERE time IN '2026'
SAMPLE BY 1w WEEK START SUNDAY
HAVING sum(volume) > 600000;

Result:

time (TIMESTAMP)symbol (SYMBOL)volume (DOUBLE)
2026-08-23T00:00:00.000ZCITY601770
2026-09-06T00:00:00.000ZCOAST690140

(2 rows)

Example: A plain column without an aggregate becomes a key (sample data: sample)

SELECT time, close
FROM ochl_day
WHERE symbol = 'CITY' AND time IN '2026-09'
SAMPLE BY 1M;

Result:

time (TIMESTAMP)close (DOUBLE)
2026-09-01T00:00:00.000Z502.4
2026-09-01T00:00:00.000Z496.4
2026-09-01T00:00:00.000Z497.9
2026-09-01T00:00:00.000Z499
2026-09-01T00:00:00.000Z498.7
2026-09-01T00:00:00.000Z497.5
2026-09-01T00:00:00.000Z504.6
2026-09-01T00:00:00.000Z504.9

(8 rows)

Example: SAMPLE BY on a relational table (sample data: sample; shows an error)

SELECT id, count(*) FROM brokers SAMPLE BY 1d;

Result:

Error: SAMPLE BY needs a table with a time column (a TIMESERIES table)

Intervals

Syntax:

SAMPLE BY n ms | n s | n m | n h | n d | n w | n M | n y

The interval is a whole number and a unit, written together without a space: 15m, 4h, 1M.

UnitMeaningBuckets start at
msmillisecondsevery n milliseconds
ssecondsevery n seconds
mminutes (small m)full minutes: :00, :15, :30, ...
hhoursfull hours (UTC, or the TIME ZONE clock)
ddays00:00 UTC (or midnight of the TIME ZONE)
wweeksMonday 00:00, or Sunday with WEEK START SUNDAY
Mmonths (capital M)the 1st day of the month, 00:00
yyears1 January, 00:00

Example: Monthly candles of VOLT (sample data: sample)

SELECT time, symbol, first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close
FROM ochl_day
WHERE symbol = 'VOLT'
SAMPLE BY 1M;

Result:

time (TIMESTAMP)symbol (SYMBOL)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)
2025-12-01T00:00:00.000ZVOLT187.7199.7187.5194.3
2026-08-01T00:00:00.000ZVOLT182.4183.6170.6172.6
2026-09-01T00:00:00.000ZVOLT171.3192.1167.3191.2

(3 rows)

Example: 1m is a minute: 5 one-minute rows of COAST (sample data: sample)

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST' AND time IN '2026-09-10'
SAMPLE BY 1m
LIMIT 5;

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)
2026-09-10T05:15:00.000Z380190
2026-09-10T05:16:00.000Z379.61330
2026-09-10T05:17:00.000Z380.11010
2026-09-10T05:18:00.000Z380.4320
2026-09-10T05:19:00.000Z380.7400

(5 rows)

Example: Yearly rows, and a 2-week bucket (sample data: sample)

SELECT time, last(close) AS close, count(*) AS days FROM ochl_day WHERE symbol = 'COAST' SAMPLE BY 1y;
SELECT time, first(open) AS open, last(close) AS close FROM ochl_day WHERE symbol = 'COAST' AND time IN '2026' SAMPLE BY 2w WEEK START SUNDAY;

Result:

SELECT time, last(close) AS close, count(*) AS days FROM ochl_day WHERE symbol = 'COAST' SAMPLE BY 1

time (TIMESTAMP)close (DOUBLE)days (LONG)
2025-01-01T00:00:00.000Z426.54
2026-01-01T00:00:00.000Z376.115

(2 rows)

SELECT time, first(open) AS open, last(close) AS close FROM ochl_day WHERE symbol = 'COAST' AND time

time (TIMESTAMP)open (DOUBLE)close (DOUBLE)
2026-08-23T00:00:00.000Z392.1373.2
2026-09-06T00:00:00.000Z374.6376.1

(2 rows)

Example: An unknown unit (sample data: sample; shows an error)

SELECT time, count(*) FROM ochl_day SAMPLE BY 1x;

Result:

Error: line 1, column 47: expected a time interval such as 1m, 1h, 1d or 1M (units: ms, s, m, h, d, w, M for month, y for year) but found '1x'

FILL

Syntax:

SAMPLE BY interval FILL(NONE)
SAMPLE BY interval FILL(NULL)
SAMPLE BY interval FILL(PREV)
SAMPLE BY interval FILL(LINEAR)
SAMPLE BY interval FILL(constant)
SAMPLE BY interval FILL(rule, rule, ...)      -- one rule per aggregate column

Says what to do with empty buckets (buckets with no rows), for example minutes when a share did not trade.

FILLAn empty bucket gets
(no FILL) or FILL(NONE)no row
FILL(NULL)a row with NULL in every aggregate column
FILL(PREV)the values of the bucket before it
FILL(LINEAR)values on a straight line between the bucket before and the bucket after
FILL(0), FILL(-1.5), FILL('n/a')that constant

Example: COAST had no trades from 12:31 to 12:34 Nepal time: no FILL, then FILL(PREV) (sample data: sample)

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST'
  AND time >= '2026-09-10T12:30:00+05:45' AND time < '2026-09-10T12:36:00+05:45'
SAMPLE BY 1m;

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST'
  AND time >= '2026-09-10T12:30:00+05:45' AND time < '2026-09-10T12:36:00+05:45'
SAMPLE BY 1m FILL(PREV);

Result:

SELECT time, last(close) AS close, sum(volume) AS volume FROM ochl_minutes WHERE symbol = 'COAST' AN

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)
2026-09-10T06:45:00.000Z380.2620
2026-09-10T06:50:00.000Z379.8800

(2 rows)

SELECT time, last(close) AS close, sum(volume) AS volume FROM ochl_minutes WHERE symbol = 'COAST' AN

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)
2026-09-10T06:45:00.000Z380.2620
2026-09-10T06:46:00.000Z380.2620
2026-09-10T06:47:00.000Z380.2620
2026-09-10T06:48:00.000Z380.2620
2026-09-10T06:49:00.000Z380.2620
2026-09-10T06:50:00.000Z379.8800

(6 rows)

Example: FILL(NULL), FILL(LINEAR) and FILL(0) on the same gap (sample data: sample)

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST' AND time >= '2026-09-10T12:30:00+05:45' AND time < '2026-09-10T12:36:00+05:45'
SAMPLE BY 1m FILL(NULL);

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST' AND time >= '2026-09-10T12:30:00+05:45' AND time < '2026-09-10T12:36:00+05:45'
SAMPLE BY 1m FILL(LINEAR);

SELECT time, last(close) AS close, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'COAST' AND time >= '2026-09-10T12:30:00+05:45' AND time < '2026-09-10T12:36:00+05:45'
SAMPLE BY 1m FILL(0);

Result:

SELECT time, last(close) AS close, sum(volume) AS volume FROM ochl_minutes WHERE symbol = 'COAST' AN

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)
2026-09-10T06:45:00.000Z380.2620
2026-09-10T06:46:00.000ZNULLNULL
2026-09-10T06:47:00.000ZNULLNULL
2026-09-10T06:48:00.000ZNULLNULL
2026-09-10T06:49:00.000ZNULLNULL
2026-09-10T06:50:00.000Z379.8800

(6 rows)

SELECT time, last(close) AS close, sum(volume) AS volume FROM ochl_minutes WHERE symbol = 'COAST' AN

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)
2026-09-10T06:45:00.000Z380.2620
2026-09-10T06:46:00.000Z380.12656
2026-09-10T06:47:00.000Z380.04692
2026-09-10T06:48:00.000Z379.96728
2026-09-10T06:49:00.000Z379.88764
2026-09-10T06:50:00.000Z379.8800

(6 rows)

SELECT time, last(close) AS close, count(*) AS minutes FROM ochl_minutes WHERE symbol = 'COAST' AND

time (TIMESTAMP)close (DOUBLE)minutes (LONG)
2026-09-10T06:45:00.000Z380.21
2026-09-10T06:46:00.000Z00
2026-09-10T06:47:00.000Z00
2026-09-10T06:48:00.000Z00
2026-09-10T06:49:00.000Z00
2026-09-10T06:50:00.000Z379.81

(6 rows)

Example: A rule per column: close from the minute before, volume 0 (sample data: sample)

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST' AND time >= '2026-09-10T12:30:00+05:45' AND time < '2026-09-10T12:36:00+05:45'
SAMPLE BY 1m FILL(PREV, 0);

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)
2026-09-10T06:45:00.000Z380.2620
2026-09-10T06:46:00.000Z380.20
2026-09-10T06:47:00.000Z380.20
2026-09-10T06:48:00.000Z380.20
2026-09-10T06:49:00.000Z380.20
2026-09-10T06:50:00.000Z379.8800

(6 rows)

Example: One rule per aggregate column, or one for all (sample data: sample; shows an error)

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes WHERE symbol = 'COAST' SAMPLE BY 1m FILL(PREV, 0, 0);

Result:

Error: FILL has 3 values but the query has 2 aggregate columns (close, volume); expected one FILL value for all of them, or one per aggregate column in SELECT order

Example: Two symbols: each symbol gets every minute of the whole range (sample data: sample)

SELECT time, symbol, last(close) AS close
FROM ochl_minutes
WHERE time >= '2026-09-10T12:30:00+05:45' AND time < '2026-09-10T12:33:00+05:45'
  AND (symbol = 'COAST' OR time >= '2026-09-10T12:32:00+05:45')
SAMPLE BY 1m FILL(PREV)
ORDER BY symbol, time;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)
2026-09-10T06:45:00.000ZCITYNULL
2026-09-10T06:46:00.000ZCITYNULL
2026-09-10T06:47:00.000ZCITY503.1
2026-09-10T06:45:00.000ZCOAST380.2
2026-09-10T06:46:00.000ZCOAST380.2
2026-09-10T06:47:00.000ZCOAST380.2

(6 rows)

Example: FILL needs a constant, not a column (sample data: sample; shows an error)

SELECT time, last(close) AS close FROM ochl_minutes WHERE symbol = 'COAST' SAMPLE BY 1m FILL(close);

Result:

Error: line 1, column 94: expected NONE, NULL, PREV, LINEAR or a constant such as 0 in FILL(...) but found 'close'

FROM / TO

Syntax:

SAMPLE BY interval FROM start TO end [FILL(...)]
SAMPLE BY interval FROM start [FILL(...)]
SAMPLE BY interval TO end [FILL(...)]

Sets the range of the buckets (QuestDB). The result runs from the bucket that holds start to the bucket before end, whatever rows there are.

PartMeaning
startthe first time: '2026-09-10T05:00:00Z', a DATE or TIMESTAMP value, $1, now(). Rows before it are left out.
endthe time after the last one (exclusive, like time < end). Rows from it on are left out.

Example: Every minute from 12:28 to 12:35 Nepal time (TO is not included) (sample data: sample)

SELECT time, last(close) AS close, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'COAST'
SAMPLE BY 1m FROM '2026-09-10T12:28:00+05:45' TO '2026-09-10T12:36:00+05:45' FILL(PREV, 0);

Result:

time (TIMESTAMP)close (DOUBLE)volume (DOUBLE)
2026-09-10T06:43:00.000Z379.4730
2026-09-10T06:44:00.000Z379.91380
2026-09-10T06:45:00.000Z380.2620
2026-09-10T06:46:00.000Z380.20
2026-09-10T06:47:00.000Z380.20
2026-09-10T06:48:00.000Z380.20
2026-09-10T06:49:00.000Z380.20
2026-09-10T06:50:00.000Z379.8800

(8 rows)

Example: FROM / TO without FILL only cut the rows (sample data: sample)

SELECT time, count(*) AS rows
FROM ochl_minutes
WHERE symbol = 'COAST'
SAMPLE BY 1h FROM '2026-09-10T11:00:00+05:45' TO '2026-09-10T13:00:00+05:45'
ALIGN TO CALENDAR TIME ZONE 'Asia/Kathmandu';

Result:

time (TIMESTAMP)rows (LONG)
2026-09-10T05:15:00.000Z60
2026-09-10T06:15:00.000Z56

(2 rows)

Example: FROM must be a time (sample data: sample; shows an error)

SELECT time, sum(volume) FROM ochl_minutes SAMPLE BY 1m FROM 'soon';

Result:

Error: line 1, column 62: SAMPLE BY FROM 'soon' is not a time; expected a time such as '2026-09-24T05:00:00Z'

ALIGN TO CALENDAR

Syntax:

SAMPLE BY interval ALIGN TO CALENDAR

Buckets start at round clock times: a 10m bucket starts at :00, :10, :20, ...; a 1d bucket at 00:00. This is the default, so you never need to write it. QuestDB queries that have it run unchanged.

Example: 10-minute buckets on the clock, the first row is at 05:17 (sample data: sample)

SELECT time, first(open) AS open, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time >= '2026-09-10T05:17:00Z' AND time < '2026-09-10T05:40:00Z'
SAMPLE BY 10m ALIGN TO CALENDAR;

Result:

time (TIMESTAMP)open (DOUBLE)minutes (LONG)
2026-09-10T05:10:00.000Z504.63
2026-09-10T05:20:00.000Z504.510
2026-09-10T05:30:00.000Z504.410

(3 rows)

ALIGN TO FIRST OBSERVATION

Syntax:

SAMPLE BY interval ALIGN TO FIRST OBSERVATION

The first bucket starts at the time of the first row (after WHERE). The next buckets follow every interval from there.

Example: The same rows: buckets start at the first row, 05:17 (sample data: sample)

SELECT time, first(open) AS open, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time >= '2026-09-10T05:17:00Z' AND time < '2026-09-10T05:40:00Z'
SAMPLE BY 10m ALIGN TO FIRST OBSERVATION;

Result:

time (TIMESTAMP)open (DOUBLE)minutes (LONG)
2026-09-10T05:17:00.000Z504.610
2026-09-10T05:27:00.000Z504.510
2026-09-10T05:37:00.000Z504.53

(3 rows)

Example: Not with months (sample data: sample; shows an error)

SELECT time, count(*) FROM ochl_day SAMPLE BY 1M ALIGN TO FIRST OBSERVATION;

Result:

Error: ALIGN TO FIRST OBSERVATION needs a fixed interval, not months or years

Example: Not with TIME ZONE (sample data: sample; shows an error)

SELECT time, count(*) FROM ochl_minutes SAMPLE BY 1h ALIGN TO FIRST OBSERVATION TIME ZONE 'Asia/Kathmandu';

Result:

Error: line 1, column 81: TIME ZONE only applies to calendar buckets; expected ALIGN TO CALENDAR (or no ALIGN TO) instead of ALIGN TO FIRST OBSERVATION

TIME ZONE

Syntax:

SAMPLE BY interval TIME ZONE 'zone'
SAMPLE BY interval TIME ZONE '+HH:MM' | '-HH:MM'

Makes the buckets follow the clock of that time zone: hours start at the full hour of that clock, days at its midnight, months at its 1st day.

ArgumentTypeMeaning
zonetext in single quotesa name like 'Asia/Kathmandu', 'NPT', 'UTC', or a fixed offset like '+05:45'. The list of names. Names do not care about case.
The table stores a day atExample: the bar of Thu 10 Sep 2026Weeks and monthsFilter one day
00:00 UTC of that date (like ochl_day in the samples)2026-09-10T00:00:00ZSAMPLE BY 1w, no TIME ZONE (with it, buckets are stamped 18:15 UTC of the day before)time IN '2026-09-10'
local midnight, for example Nepal 00:002026-09-09T18:15:00ZSAMPLE BY 1w TIME ZONE 'Asia/Kathmandu'time IN '2026-09-10' TIME ZONE 'Asia/Kathmandu'

Warning, local-midnight rows: in UTC these rows belong to the day before. Everything that cuts days needs the zone, and nothing gives an error when it is left out:

Without the zone (wrong for these rows)With the zone
SAMPLE BY 1w starts each week at 00:00 UTC (05:45 in Nepal). The first bar of each week goes into the week before.SAMPLE BY 1w TIME ZONE 'Asia/Kathmandu'
time IN '2026-09-10' does not find the bar of 10 Sep; it is in time IN '2026-09-09'.time IN '2026-09-10' TIME ZONE 'Asia/Kathmandu' (still reads only the partitions of that day)
A column TIMESTAMP ROUND 1d cuts to the UTC day: '2026-09-10T00:00:00+05:45' is stored as 2026-09-09T00:00:00Z, and the Nepal date is lost.TIMESTAMP ROUND 1d TIME ZONE 'Asia/Kathmandu' keeps Nepal midnight
date_trunc('day', time) gives UTC midnight.date_trunc('day', time, 'Asia/Kathmandu') or timestamp_floor('1d', time, 'Asia/Kathmandu'): Nepal midnight, shown in UTC

The week mistake is the easiest to miss, because the result looks normal.

Example: 1-hour candles: UTC hours, then Nepal hours (sample data: sample)

SELECT time, first(open) AS open, last(close) AS close, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
SAMPLE BY 1h;

SELECT time, first(open) AS open, last(close) AS close, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
SAMPLE BY 1h TIME ZONE 'Asia/Kathmandu';

Result:

SELECT time, first(open) AS open, last(close) AS close, count(*) AS minutes FROM ochl_minutes WHERE

time (TIMESTAMP)open (DOUBLE)close (DOUBLE)minutes (LONG)
2026-09-10T05:00:00.000Z504.6506.445
2026-09-10T06:00:00.000Z506.4501.260
2026-09-10T07:00:00.000Z501.2502.660
2026-09-10T08:00:00.000Z502.650560
2026-09-10T09:00:00.000Z505504.915

(5 rows)

SELECT time, first(open) AS open, last(close) AS close, count(*) AS minutes FROM ochl_minutes WHERE

time (TIMESTAMP)open (DOUBLE)close (DOUBLE)minutes (LONG)
2026-09-10T05:15:00.000Z504.6505.360
2026-09-10T06:15:00.000Z505.3502.760
2026-09-10T07:15:00.000Z502.7502.660
2026-09-10T08:15:00.000Z502.6504.960

(4 rows)

Example: Nepal days: the bucket starts at Nepal midnight (18:15 UTC) (sample data: sample)

SELECT time, symbol, first(open) AS open, last(close) AS close, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY'
SAMPLE BY 1d TIME ZONE '+05:45';

Result:

time (TIMESTAMP)symbol (SYMBOL)open (DOUBLE)close (DOUBLE)minutes (LONG)
2026-09-08T18:15:00.000ZCITY512504.6240
2026-09-09T18:15:00.000ZCITY504.6504.9240

(2 rows)

Example: India time for the same rows (UTC+05:30) (sample data: sample)

SELECT time, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
SAMPLE BY 1h TIME ZONE 'Asia/Kolkata';

Result:

time (TIMESTAMP)minutes (LONG)
2026-09-10T04:30:00.000Z15
2026-09-10T05:30:00.000Z60
2026-09-10T06:30:00.000Z60
2026-09-10T07:30:00.000Z60
2026-09-10T08:30:00.000Z45

(5 rows)

Example: The same five trading days, stored at UTC midnight and at Nepal midnight (sample data: sample)

Setup:

CREATE TABLE bars_utc (time TIMESTAMP, symbol SYMBOL, close DOUBLE) TIMESERIES(time) PARTITION BY MONTH;
CREATE TABLE bars_local (time TIMESTAMP, symbol SYMBOL, close DOUBLE) TIMESERIES(time) PARTITION BY MONTH;
INSERT INTO bars_utc VALUES ('2026-09-06T00:00:00Z', 'ACME', 1), ('2026-09-07T00:00:00Z', 'ACME', 2),
  ('2026-09-10T00:00:00Z', 'ACME', 3), ('2026-09-13T00:00:00Z', 'ACME', 4), ('2026-09-14T00:00:00Z', 'ACME', 5);
INSERT INTO bars_local VALUES ('2026-09-06T00:00:00+05:45', 'ACME', 1), ('2026-09-07T00:00:00+05:45', 'ACME', 2),
  ('2026-09-10T00:00:00+05:45', 'ACME', 3), ('2026-09-13T00:00:00+05:45', 'ACME', 4), ('2026-09-14T00:00:00+05:45', 'ACME', 5);
-- UTC midnight rows: no TIME ZONE. Weeks start Sunday 6 and Sunday 13 (00:00 UTC).
SELECT time, count(*) AS days, sum(close) AS total FROM bars_utc SAMPLE BY 1w WEEK START SUNDAY;

-- Nepal midnight rows WITHOUT TIME ZONE: wrong. Sunday bars fall into the week before.
SELECT time, count(*) AS days, sum(close) AS total FROM bars_local SAMPLE BY 1w WEEK START SUNDAY;

-- Nepal midnight rows WITH TIME ZONE: right. 18:15 UTC on Saturday is Sunday 00:00 in Nepal.
SELECT time, count(*) AS days, sum(close) AS total FROM bars_local SAMPLE BY 1w WEEK START SUNDAY TIME ZONE 'Asia/Kathmandu';

Result:

SELECT time, count(*) AS days, sum(close) AS total FROM bars_utc SAMPLE BY 1w WEEK START SUNDAY

time (TIMESTAMP)days (LONG)total (DOUBLE)
2026-09-06T00:00:00.000Z36
2026-09-13T00:00:00.000Z29

(2 rows)

SELECT time, count(*) AS days, sum(close) AS total FROM bars_local SAMPLE BY 1w WEEK START SUNDAY

time (TIMESTAMP)days (LONG)total (DOUBLE)
2026-08-30T00:00:00.000Z11
2026-09-06T00:00:00.000Z39
2026-09-13T00:00:00.000Z15

(3 rows)

SELECT time, count(*) AS days, sum(close) AS total FROM bars_local SAMPLE BY 1w TIME ZONE 'Asia/Kath

time (TIMESTAMP)days (LONG)total (DOUBLE)
2026-09-05T18:15:00.000Z36
2026-09-12T18:15:00.000Z29

(2 rows)

Example: Nepal midnight rows: find one Nepal day and show the Nepal date (sample data: sample)

Setup:

CREATE TABLE bars_local (time TIMESTAMP, symbol SYMBOL, close DOUBLE) TIMESERIES(time) PARTITION BY MONTH;
INSERT INTO bars_local VALUES ('2026-09-10T00:00:00+05:45', 'CITY', 512.4), ('2026-09-13T00:00:00+05:45', 'CITY', 507.6);
-- The UTC date: finds nothing.
SELECT time, close FROM bars_local WHERE time IN '2026-09-10';

-- The Nepal date: finds the bar.
SELECT time, close,
       to_char(time, 'YYYY-MM-DD', 'Asia/Kathmandu') AS nepal_date,
       date_trunc('day', time) AS utc_midnight,
       date_trunc('day', time, 'Asia/Kathmandu') AS nepal_midnight
FROM bars_local WHERE time IN '2026-09-10' TIME ZONE 'Asia/Kathmandu';

Result:

SELECT time, close FROM bars_local WHERE time IN '2026-09-10'

time (TIMESTAMP)close (DOUBLE)

(0 rows)

SELECT time, close, to_char(time, 'YYYY-MM-DD', 'Asia/Kathmandu') AS nepal_date, date_trunc('day', t

time (TIMESTAMP)close (DOUBLE)nepal_date (STRING)utc_midnight (TIMESTAMP)nepal_midnight (TIMESTAMP)
2026-09-09T18:15:00.000Z512.42026-09-102026-09-09T00:00:00.000Z2026-09-09T18:15:00.000Z

(1 row)

Example: ROUND 1d cuts to the UTC day; ROUND 1d TIME ZONE keeps the Nepal date (sample data: sample)

Setup:

CREATE TABLE bars_round (time TIMESTAMP ROUND 1d, symbol SYMBOL, close DOUBLE) TIMESERIES(time) PARTITION BY YEAR;
CREATE TABLE bars_nepal (time TIMESTAMP ROUND 1d TIME ZONE 'Asia/Kathmandu', symbol SYMBOL, close DOUBLE)
  TIMESERIES(time) PARTITION BY YEAR DEDUP KEY (symbol, time);
-- 9 Sep 00:00 UTC: the Nepal date 10 Sep is lost.
INSERT INTO bars_round VALUES ('2026-09-10T00:00:00+05:45', 'CITY', 512.4);
SELECT time, close FROM bars_round;

-- Nepal midnight (18:15 UTC the day before). 15:00 the same Nepal day is the same key, so it replaces the bar.
INSERT INTO bars_nepal VALUES ('2026-09-10T00:00:00+05:45', 'CITY', 512.4);
INSERT OR REPLACE INTO bars_nepal VALUES ('2026-09-10T15:00:00+05:45', 'CITY', 514.0);
SELECT time, close, to_char(time, 'YYYY-MM-DD', 'Asia/Kathmandu') AS nepal_date FROM bars_nepal;

Result:

INSERT INTO bars_round VALUES ('2026-09-10T00:00:00+05:45', 'CITY', 512.4)

1 row(s) inserted

SELECT time, close FROM bars_round

time (TIMESTAMP)close (DOUBLE)
2026-09-09T00:00:00.000Z512.4

(1 row)

INSERT INTO bars_nepal VALUES ('2026-09-10T00:00:00+05:45', 'CITY', 512.4)

1 row(s) inserted

INSERT OR REPLACE INTO bars_nepal VALUES ('2026-09-10T15:00:00+05:45', 'CITY', 514.0)

0 row(s) inserted, 1 replaced

SELECT time, close, to_char(time, 'YYYY-MM-DD', 'Asia/Kathmandu') AS nepal_date FROM bars_nepal

time (TIMESTAMP)close (DOUBLE)nepal_date (STRING)
2026-09-09T18:15:00.000Z5142026-09-10

(1 row)

Example: ROUND TIME ZONE takes fixed offsets only, for now (sample data: sample; shows an error)

CREATE TABLE bars_london (time TIMESTAMP ROUND 1d TIME ZONE 'Europe/London', close DOUBLE) TIMESERIES(time);

Result:

Error: column 'time' of table 'bars_london' has ROUND ... TIME ZONE 'Europe/London', which is not supported (it may change for daylight saving); use a fixed offset like '+05:45'

Example: Hour buckets on the London clock (summer time: +01:00 in September) (sample data: sample)

SELECT time, count(*) AS minutes FROM ochl_minutes WHERE symbol = 'COAST' AND time IN '2026-09-10'
SAMPLE BY 1h TIME ZONE 'Europe/London' LIMIT 3;

Result:

time (TIMESTAMP)minutes (LONG)
2026-09-10T05:00:00.000Z45
2026-09-10T06:00:00.000Z56
2026-09-10T07:00:00.000Z60

(3 rows)

WITH OFFSET

Syntax:

SAMPLE BY interval WITH OFFSET '+HH:MM'
SAMPLE BY interval WITH OFFSET '-HH:MM'

Moves the start of every bucket by a fixed time. SAMPLE BY 1h WITH OFFSET '+00:15' makes hours that start at :15 (UTC), which are the full hours of the Nepal clock.

ArgumentTypeMeaning
'+HH:MM'text in single quoteshours 00 to 23, minutes 00 to 59. + moves later, - moves earlier. '00:15' without a sign means '+00:15' (the QuestDB spelling).

Example: Nepal-clock hours with an offset, written two ways (sample data: sample)

SELECT time, first(open) AS open, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
SAMPLE BY 1h ALIGN TO CALENDAR WITH OFFSET '00:15';

SELECT time, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
SAMPLE BY 1h WITH OFFSET '-00:45';

Result:

SELECT time, first(open) AS open, count(*) AS minutes FROM ochl_minutes WHERE symbol = 'CITY' AND ti

time (TIMESTAMP)open (DOUBLE)minutes (LONG)
2026-09-10T05:15:00.000Z504.660
2026-09-10T06:15:00.000Z505.360
2026-09-10T07:15:00.000Z502.760
2026-09-10T08:15:00.000Z502.660

(4 rows)

SELECT time, count(*) AS minutes FROM ochl_minutes WHERE symbol = 'CITY' AND time IN '2026-09-10' SA

time (TIMESTAMP)minutes (LONG)
2026-09-10T05:15:00.000Z60
2026-09-10T06:15:00.000Z60
2026-09-10T07:15:00.000Z60
2026-09-10T08:15:00.000Z60

(4 rows)

Example: Careful: an offset on days is not Nepal midnight (sample data: sample)

SELECT time, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY'
SAMPLE BY 1d WITH OFFSET '+05:45';

Result:

time (TIMESTAMP)minutes (LONG)
2026-09-08T05:45:00.000Z30
2026-09-09T05:45:00.000Z240
2026-09-10T05:45:00.000Z210

(3 rows)

Example: TIME ZONE and WITH OFFSET together: Nepal hours that start at half past (10:30, 11:30, ...) (sample data: sample)

SELECT time, first(open) AS open, last(close) AS close, count(*) AS minutes
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
SAMPLE BY 1h TIME ZONE 'Asia/Kathmandu' WITH OFFSET '+00:30';

Result:

time (TIMESTAMP)open (DOUBLE)close (DOUBLE)minutes (LONG)
2026-09-10T04:45:00.000Z504.6506.530
2026-09-10T05:45:00.000Z506.5503.860
2026-09-10T06:45:00.000Z503.8503.660
2026-09-10T07:45:00.000Z503.6505.760
2026-09-10T08:45:00.000Z505.7504.930

(5 rows)

Example: The hour needs two digits (sample data: sample; shows an error)

SELECT time, count(*) FROM ochl_minutes SAMPLE BY 1h WITH OFFSET '5:45';

Result:

Error: line 1, column 66: expected an offset in single quotes such as '+00:15' or '-05:45' after WITH OFFSET but found '5:45'

WEEK START

Syntax:

SAMPLE BY n w WEEK START SUNDAY
SAMPLE BY n w WEEK START MONDAY

Chooses the first day of a week bucket. The default is MONDAY. On a Sunday-to-Thursday trading week (as on some exchanges), weekly candles need WEEK START SUNDAY.

Example: Sunday-to-Thursday trading weeks of CITY (sample data: sample)

SELECT time, symbol,
       first(open) AS open, max(high) AS high, min(low) AS low, last(close) AS close,
       sum(volume) AS volume, count(*) AS days
FROM ochl_day
WHERE symbol = 'CITY' AND time IN '2026'
SAMPLE BY 1w WEEK START SUNDAY;

Result:

time (TIMESTAMP)symbol (SYMBOL)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)days (LONG)
2026-08-23T00:00:00.000ZCITY505517502505.96017705
2026-08-30T00:00:00.000ZCITY507.3511.2494.1497.95381305
2026-09-06T00:00:00.000ZCITY497.4512.4495504.95918605

(3 rows)

With the default Monday weeks, Sunday 23 August goes into the week of Monday 17 August, alone:

Example: The default week starts on Monday (sample data: sample)

SELECT time, first(open) AS open, last(close) AS close, count(*) AS days
FROM ochl_day
WHERE symbol = 'CITY' AND time IN '2026'
SAMPLE BY 1w;

Result:

time (TIMESTAMP)open (DOUBLE)close (DOUBLE)days (LONG)
2026-08-17T00:00:00.000Z505507.91
2026-08-24T00:00:00.000Z508.3503.15
2026-08-31T00:00:00.000Z502.54995
2026-09-07T00:00:00.000Z497.3504.94

(4 rows)

Example: WEEK START with an hour interval (sample data: sample; shows an error)

SELECT time, count(*) FROM ochl_minutes SAMPLE BY 1h WEEK START SUNDAY;

Result:

Error: line 1, column 54: WEEK START needs a week interval; expected SAMPLE BY 1w (or 2w, ...) but the interval is 1h