KDB knowledge

WHERE

Keep only the rows you want, with comparisons, IN, BETWEEN, LIKE, IS NULL and time ranges.

WHERE keeps the rows where the condition is true. Rows where it is false or NULL are left out.

On a time-series table, a condition on the time column (like time IN '2026-09') also makes the query fast: KDB does not read the other partitions (files). A condition like symbol = 'CITY' lets KDB jump straight to that symbol's rows. Some shapes of condition are much faster than others: see fast conditions.

Example: CITY and VOLT closes above 190 in September 2026 (sample data: sample)

SELECT time, symbol, close
FROM ochl_day
WHERE symbol IN ('CITY', 'VOLT') AND close > 190 AND time IN '2026-09'
ORDER BY time, symbol
LIMIT 5;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)
2026-09-01T00:00:00.000ZCITY502.4
2026-09-02T00:00:00.000ZCITY496.4
2026-09-03T00:00:00.000ZCITY497.9
2026-09-06T00:00:00.000ZCITY499
2026-09-07T00:00:00.000ZCITY498.7

(5 rows)

WHERE

Syntax:

SELECT ... FROM ... WHERE condition
UPDATE table SET ... WHERE condition
DELETE FROM table WHERE condition

The condition is an expression that gives true or false. You can use columns of every table in FROM and JOIN, functions, and subqueries.

Example: Pro users of the app (sample data: query-app)

SELECT id, username, city
FROM app_users
WHERE plan = 'pro';

Result:

id (INT)username (STRING)city (STRING)
1ramKathmandu
4gitaDharan
7sumanPokhara

(3 rows)

Example: Ledger lines of the bank account in the first week of September (sample data: query-app)

SELECT entry_date, debit, credit, memo
FROM ledger
WHERE account = 1100 AND entry_date >= '2026-09-01' AND entry_date < '2026-09-08'
ORDER BY line_id;

Result:

entry_date (TIMESTAMP)debit (DECIMAL(2))credit (DECIMAL(2))memo (STRING)
2026-09-01T00:00:00.000Z500000.00NULLOwner puts money in the bank
2026-09-03T00:00:00.000ZNULL257500.00Pay the broker
2026-09-03T00:00:00.000ZNULL1030.00Commission on the CITY buy
2026-09-04T00:00:00.000ZNULL30000.00Take cash from the bank

(4 rows)

Example: Forum posts written by sita (sample data: content-forum)

SELECT id, thread_id, body
FROM posts
WHERE author = 'sita';

Result:

id (LONG)thread_id (LONG)body (STRING)
21I agree, the profit grew 18%.
61सिटी बैंकको नाफा राम्रो छ।

(2 rows)

Example: The condition must be true or false (sample data: sample; shows an error)

SELECT symbol FROM ochl_day WHERE close;

Result:

Error: line 1, column 35: a condition must be true or false, got '542.2'

Example: An alias is not known in WHERE (sample data: sample; shows an error)

SELECT symbol, close - open AS change FROM ochl_day WHERE change > 0;

Result:

Error: line 1, column 59: unknown column 'change' (table 'ochl_day' has: time symbol open high low close volume)

AND, OR, NOT

Syntax:

condition AND condition
condition OR condition
NOT condition
OperatorTrue when
a AND bboth are true
a OR bat least one is true
NOT aa is false

Example: Without ( ): the price and the day apply only to COAST, so CITY rows of every day come back (sample data: sample)

SELECT time, symbol, close
FROM ochl_day
WHERE symbol = 'CITY' OR symbol = 'COAST' AND close > 400
  AND time IN '2026-09-10'
LIMIT 4;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)
2025-12-28T00:00:00.000ZCITY542.2
2025-12-29T00:00:00.000ZCITY539.7
2025-12-30T00:00:00.000ZCITY539.3
2025-12-31T00:00:00.000ZCITY542.4

(4 rows)

Example: The same with ( ): now the time and price apply to both symbols (sample data: sample)

SELECT time, symbol, close
FROM ochl_day
WHERE (symbol = 'CITY' OR symbol = 'COAST') AND close > 400
  AND time IN '2026-09-10';

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)
2026-09-10T00:00:00.000ZCITY504.9

(1 row)

Example: Trades where broker 58 was the buyer or the seller, but not with broker 1 (sample data: sample)

SELECT transaction_id, symbol, buyer, seller, quantity
FROM trade_log
WHERE (buyer = 58 OR seller = 58) AND NOT (buyer = 1 OR seller = 1);

Result:

transaction_id (LONG)symbol (SYMBOL)buyer (INT)seller (INT)quantity (LONG)
100006COAST5845200
100001TELCO58341000
100030CITY455820
100019COAST583410
100031COAST45581000
100012VOLT5845200

(6 rows)

Example (sample data: sample)

SELECT TRUE AND NULL AS a, FALSE AND NULL AS b, TRUE OR NULL AS c, FALSE OR NULL AS d, NOT NULL AS e;

Result:

a (STRING)b (BOOL)c (BOOL)d (STRING)e (STRING)
NULLfalsetrueNULLNULL

(1 row)

=, ==, !=, <>, <, <=, >, >=

Syntax:

a = b     a == b
a != b    a <> b
a < b     a <= b     a > b     a >= b

Compare two values. == is the same as =, and <> is the same as !=.

LeftRightHow they are compared
numbernumberby value (5 = 5.0 is true)
texttextletter by letter, by byte: capital letters come before small letters, and case matters
TIMESTAMP or DATEtextthe text is read as a time: time >= '2026-09-01'
numbertext that is a numberas numbers: id = '34' is true for 34
texttext that is not a number, with a numbererror cannot compare

Example: The first 5 minutes after the market opened (11:00 Nepal time) (sample data: sample)

SELECT time, symbol, close
FROM ochl_minutes
WHERE symbol = 'CITY'
  AND time >= '2026-09-10T11:00:00+05:45'
  AND time <  '2026-09-10T11:05:00+05:45';

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)
2026-09-10T05:15:00.000ZCITY504.4
2026-09-10T05:16:00.000ZCITY504.6
2026-09-10T05:17:00.000ZCITY504.2
2026-09-10T05:18:00.000ZCITY504.4
2026-09-10T05:19:00.000ZCITY504.5

(5 rows)

Example: Big trades: 1000 shares or more (sample data: sample)

SELECT transaction_id, symbol, quantity, rate FROM trade_log WHERE quantity >= 1000;

Result:

transaction_id (LONG)symbol (SYMBOL)quantity (LONG)rate (DOUBLE)
100001TELCO1000881
100007TELCO1000879
100015COAST1000398
100031COAST1000402

(4 rows)

Example (sample data: sample)

SELECT 5 = 5.0 AS a, 5 == 5 AS b, 'CITY' <> 'city' AS c, 'B' < 'a' AS d, 34 = '34' AS e;

Result:

a (BOOL)b (BOOL)c (BOOL)d (BOOL)e (BOOL)
truetruetruetruetrue

(1 row)

Example: Text that is not a number cannot be compared with a number (sample data: sample; shows an error)

SELECT * FROM brokers WHERE name = 58;

Result:

Error: line 1, column 34: cannot compare STRING 'Keystone Securities' with LONG '58'

Example: A time must be written YYYY-MM-DD (sample data: sample; shows an error)

SELECT time, close FROM ochl_day WHERE time >= '2026-9-1';

Result:

Error: line 1, column 45: cannot compare TIMESTAMP '2025-12-28T00:00:00.000Z' with STRING '2026-9-1'

BETWEEN, NOT BETWEEN

Syntax:

value [NOT] BETWEEN low AND high

x BETWEEN a AND b is the same as x >= a AND x <= b. Both ends are included.

ArgumentTypeMeaning
valuenumber, text or timethe value to test
low, highthe same kindthe smallest and the biggest allowed value

Example: Days when COAST closed between 375 and 380 (sample data: sample)

SELECT time, close FROM ochl_day
WHERE symbol = 'COAST' AND close BETWEEN 375 AND 380;

Result:

time (TIMESTAMP)close (DOUBLE)
2026-08-25T00:00:00.000Z379.8
2026-09-06T00:00:00.000Z375.4
2026-09-07T00:00:00.000Z379.2
2026-09-08T00:00:00.000Z377.2
2026-09-10T00:00:00.000Z376.1

(5 rows)

Example: BETWEEN with times: the end is the start of the last day (sample data: sample)

SELECT time, symbol, close FROM ochl_day
WHERE symbol = 'VOLT' AND time BETWEEN '2026-09-07' AND '2026-09-09';

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)
2026-09-07T00:00:00.000ZVOLT176.9
2026-09-08T00:00:00.000ZVOLT182.4
2026-09-09T00:00:00.000ZVOLT187.3

(3 rows)

Example: Minutes where CITY was outside its normal band (sample data: sample)

SELECT time, close FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10' AND close NOT BETWEEN 501 AND 507;

Result:

time (TIMESTAMP)close (DOUBLE)
2026-09-10T06:57:00.000Z500.9

(1 row)

IN (list), NOT IN (list)

Syntax:

value [NOT] IN (value, value, ...)
value [NOT] IN (SELECT ...)

True when the value is equal to one of the values in the list. For a list from a query, see IN (SELECT ...).

Example: Banks only (sample data: sample)

SELECT symbol, name FROM securities WHERE symbol IN ('CITY', 'COAST');

Result:

symbol (SYMBOL)name (STRING)
CITYCity Bank Limited
COASTCoastal Bank Limited

(2 rows)

Example: Journal lines that are not income or expense (sample data: query-app)

SELECT line_id, account, debit, credit FROM ledger
WHERE account NOT IN (4000, 4100, 5000, 5100) AND entry_id >= 7;

Result:

line_id (INT)account (INT)debit (DECIMAL(2))credit (DECIMAL(2))
1311006250.00NULL
15110012600.00NULL
161200NULL10300.00

(3 rows)

Example: NULL in the list (sample data: sample)

SELECT 1 IN (1, NULL) AS found, 2 IN (1, NULL) AS not_found, 2 NOT IN (1, NULL) AS not_in;

Result:

found (BOOL)not_found (STRING)not_in (STRING)
trueNULLNULL

(1 row)

Example: A list needs ( ) (sample data: sample; shows an error)

SELECT * FROM securities WHERE symbol IN 'CITY';

Result:

Error: line 1, column 42: expected a time interval string such as '2026-09', '2026-09-11T10:00' or '2026-09-11;1d' after IN (for a list of values write IN ('a', 'b')) but found 'CITY'

time IN 'interval'

Syntax:

time_column [NOT] IN 'YYYY'
time_column [NOT] IN 'YYYY-MM'
time_column [NOT] IN 'YYYY-MM-DD'
time_column [NOT] IN 'YYYY-MM-DDTHH[:MM[:SS]]'
time_column [NOT] IN 'text+05:45'
time_column [NOT] IN 'text' TIME ZONE 'zone'
time_column [NOT] IN 'text;stretch'
time_column [NOT] IN 'text;stretch;period;count'
time_column [NOT] IN today() | yesterday() | tomorrow() | interval(from, to)

A text after IN (without ( )) means the whole period that the text names: a year, a month, a day, an hour, a minute or a second.

WrittenRows fromUp to (not included)
time IN '2026'2026-01-01 00:00 UTC2027-01-01 00:00 UTC
time IN '2026-09'2026-09-01 00:00 UTC2026-10-01 00:00 UTC
time IN '2026-09-10'2026-09-10 00:00 UTC2026-09-11 00:00 UTC
time IN '2026-09-10T11'11:00 UTC12:00 UTC
time IN '2026-09-10+05:45'2026-09-10 00:00 Nepal time (09-09 18:15 UTC)2026-09-11 00:00 Nepal time
time IN '2026-09-10T11+05:45'11:00 Nepal time12:00 Nepal time
time IN '2026-09-10' TIME ZONE 'Asia/Kathmandu'2026-09-10 00:00 Nepal time2026-09-11 00:00 Nepal time
time IN '2026-09' TIME ZONE 'Asia/Kathmandu'1 September 00:00 Nepal time1 October 00:00 Nepal time

Example: All daily candles of September 2026, per symbol (sample data: sample)

SELECT symbol, count(*) AS days, min(low) AS lowest, max(high) AS highest
FROM ochl_day
WHERE time IN '2026-09'
GROUP BY symbol;

Result:

symbol (SYMBOL)days (LONG)lowest (DOUBLE)highest (DOUBLE)
CITY8494.1512.4
COAST8364.1398.8
VOLT8167.3192.1

(3 rows)

Example: The 12:00 Nepal hour of CITY on 2026-09-10 (sample data: sample)

SELECT count(*) AS minutes, min(time) AS first_minute, max(time) AS last_minute
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10T12+05:45';

Result:

minutes (LONG)first_minute (TIMESTAMP)last_minute (TIMESTAMP)
602026-09-10T06:15:00.000Z2026-09-10T07:14:00.000Z

(1 row)

Example: Users who joined in September by Nepal date (sample data: query-app)

SELECT username, created_at
FROM app_users
WHERE created_at IN '2026-09+05:45';

Result:

username (STRING)created_at (TIMESTAMP)
test_user2026-08-31T18:15:00.000Z

(1 row)

Example: Everything except 2026 (sample data: sample)

SELECT count(*) AS old_rows FROM ochl_day WHERE time NOT IN '2026';

Result:

old_rows (LONG)
12

(1 row)

Example: Only times can be IN a period (sample data: sample; shows an error)

SELECT * FROM ochl_day WHERE symbol IN '2026';

Result:

Error: line 1, column 37: IN '...' needs a time or date on the left

Interval lists, today() and DECLARE

Syntax:

time_column IN 'text;stretch'
time_column IN 'text;stretch;period;count'
time_column IN today() | yesterday() | tomorrow() | interval(from, to)
DECLARE @name := value [, @name := value ...] SELECT ... @name ...

The QuestDB forms of a time filter. Each part after a ; is a count and a unit: T millisecond, s second, m minute, h hour, d day, w week, M month, y year.

WrittenRows
time IN '2026-09-10;1d'the day 2026-09-10 and one more day: to the end of 2026-09-11
time IN '2026-09-10T05:15;30m'05:15 to the end of 05:45
time IN '2026-09-07T05:15;1h;1d;5'05:15 to the end of 06:15, on five days in a row
time IN '2026-09-01;1d;1w;4'two days at the start of four weeks in a row
time IN today()today, 00:00 to 23:59:59.999 UTC
time IN yesterday('Asia/Kathmandu')yesterday on the Nepal clock
time IN interval('2026-09-10T05:15', '2026-09-10T06:00')from the first time to the second, both included

Example: A day and the day after, with ;1d (sample data: sample)

SELECT symbol, count(*) AS days FROM ochl_day WHERE time IN '2026-09-09;1d' GROUP BY symbol ORDER BY symbol;

Result:

symbol (SYMBOL)days (LONG)
CITY2
COAST2
VOLT2

(3 rows)

Example: The first hour of trading on five days (05:15 to 06:15 UTC) (sample data: sample)

SELECT timestamp_floor('1d', time) AS day, count(*) AS minutes, min(time) AS first_minute, max(time) AS last_minute
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-07T05:15;59m;1d;5'
GROUP BY timestamp_floor('1d', time)
ORDER BY day;

Result:

day (TIMESTAMP)minutes (LONG)first_minute (TIMESTAMP)last_minute (TIMESTAMP)
2026-09-09T00:00:00.000Z602026-09-09T05:15:00.000Z2026-09-09T06:14:00.000Z
2026-09-10T00:00:00.000Z602026-09-10T05:15:00.000Z2026-09-10T06:14:00.000Z

(2 rows)

Example: Today's live prices (sample data: sample)

SELECT count(*) AS rows_today FROM live_data WHERE timestamp IN today();

Result:

rows_today (LONG)
0

(1 row)

Example: interval(from, to): both ends are included (sample data: sample)

SELECT count(*) AS minutes FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN interval('2026-09-10T05:15:00Z', '2026-09-10T05:19:00Z');

Result:

minutes (LONG)
5

(1 row)

Example: DECLARE names a value for the query (sample data: sample)

DECLARE @day := '2026-09-10', @sym := 'CITY'
SELECT symbol, close FROM ochl_day WHERE time IN @day AND symbol = @sym;

Result:

symbol (SYMBOL)close (DOUBLE)
CITY504.9

(1 row)

Example: A count is needed after a period (sample data: sample; shows an error)

SELECT count(*) FROM ochl_day WHERE time IN '2026-09-10;1d;1d';

Result:

Error: line 1, column 42: '2026-09-10;1d;1d' has a period but no count: write time;stretch;period;count, as in '2026-09-11T10:00;30m;1d;5'

LIKE, NOT LIKE

Syntax:

text [NOT] LIKE 'pattern'

Matches text with a simple pattern.

In the patternMatches
%any text, also empty text
_exactly one character (also one letter of another language, like Nepali)
\%, \_, \\the character %, _ or \ itself
any other characteritself; case matters

Example: Securities with Bank in the name (sample data: sample)

SELECT symbol, name FROM securities WHERE name LIKE '%Bank%';

Result:

symbol (SYMBOL)name (STRING)
CITYCity Bank Limited
COASTCoastal Bank Limited

(2 rows)

Example: Gmail users (case matters: GITA.RAI@Gmail.com is not found) (sample data: query-app)

SELECT username, email FROM app_users WHERE email LIKE '%@gmail.com';

Result:

username (STRING)email (STRING)
ramram.sharma@gmail.com
hari_99hari99@gmail.com
sumansuman.gurung@gmail.com

(3 rows)

Example: Usernames of exactly 4 characters (sample data: query-app)

SELECT username FROM app_users WHERE username LIKE '____';

Result:

username (STRING)
sita
gita

(2 rows)

Example: Posts that do not say CITY (sample data: content-forum)

SELECT id, author, body FROM posts WHERE body NOT LIKE '%CITY%';

Result:

id (LONG)author (STRING)body (STRING)
2sitaI agree, the profit grew 18%.
3hariPrice is high now. Wait for a dip.
4gitaWhat about the bonus shares?
5ramBonus is 10% this time.
6sitaसिटी बैंकको नाफा राम्रो छ।
7hariTwo hydro IPOs open next week.
8gitaWhich ones?

(7 rows)

Example: Usernames with a real _ in them: write _ (sample data: query-app)

SELECT username FROM app_users WHERE username LIKE '%\_%';

Result:

username (STRING)
hari_99
test_user

(2 rows)

Example: A price of 100% or more in a note: \% is the % sign (sample data: sample)

SELECT '100% up' LIKE '100\%%' AS a, '1000 up' LIKE '100\%%' AS b;

Result:

a (BOOL)b (BOOL)
truefalse

(1 row)

ILIKE, NOT ILIKE

Syntax:

text [NOT] ILIKE 'pattern'

Like LIKE, but English letters match in any case: 'City' ILIKE 'CIT%' is true.

Example: Gmail users in any case (sample data: query-app)

SELECT username, email FROM app_users WHERE email ILIKE '%@gmail.com';

Result:

username (STRING)email (STRING)
ramram.sharma@gmail.com
hari_99hari99@gmail.com
gitaGITA.RAI@Gmail.com
sumansuman.gurung@gmail.com

(4 rows)

Example: Search securities by a word the user typed (sample data: sample)

SELECT symbol, name FROM securities WHERE name ILIKE '%hydro%' OR symbol ILIKE 'acme';

Result:

symbol (SYMBOL)name (STRING)
ACMEAcme Investment Company
VOLTVolt River Hydropower Limited

(2 rows)

Example (sample data: sample)

SELECT 'City' ILIKE 'CIT%' AS a, 'City' NOT ILIKE 'cit%' AS b, 'City' LIKE 'CIT%' AS c;

Result:

a (BOOL)b (BOOL)c (BOOL)
truefalsefalse

(1 row)

SIMILAR TO, NOT SIMILAR TO

Syntax:

text [NOT] SIMILAR TO 'pattern' [ESCAPE 'c']

A pattern between LIKE and a regular expression. The whole text must match.

In the patternMeans
%any text, also none
_any one character
a|ba or b
(...)a group
* + ? {2} {2,4}repeat the thing before
[abc] [a-z]one of these characters
. ^ $just the character
\%a real % (\ is the escape; ESCAPE '#' picks another one)

Example: Codes that are two letters and then 2 to 4 digits (sample data: sample)

SELECT code, code SIMILAR TO '[A-Z]{2}[0-9]{2,4}' AS ok
FROM (VALUES ('NP123'), ('N1234'), ('AB12'), ('AB12345')) AS c(code);

Result:

code (STRING)ok (BOOL)
NP123true
N1234false
AB12true
AB12345false

(4 rows)

Example: One word or another, anywhere in the text (sample data: sample)

SELECT 'hydro power' SIMILAR TO '%(hydro|solar)%' AS a, 'wind' NOT SIMILAR TO '%(hydro|solar)%' AS b,
       '50%' SIMILAR TO '50#%' ESCAPE '#' AS c;

Result:

a (BOOL)b (BOOL)c (BOOL)
truetruetrue

(1 row)

IS NULL, IS NOT NULL

Syntax:

value IS NULL
value IS NOT NULL

NULL means "no value". IS NULL is true when there is no value. IS NOT NULL is true when there is one.

Example: Users who never logged in (sample data: query-app)

SELECT id, username, created_at FROM app_users WHERE last_login IS NULL;

Result:

id (INT)username (STRING)created_at (TIMESTAMP)
3hari_992026-03-20T13:00:00.000Z
8test_user2026-08-31T18:15:00.000Z

(2 rows)

Example: News about the whole market (no symbol) (sample data: content-news)

SELECT id, title FROM news WHERE symbol IS NULL;

Result:

id (LONG)title (STRING)
4Market index closes at 2,840

(1 row)

Example: Credit lines of the ledger (sample data: query-app)

SELECT line_id, account, credit, memo FROM ledger WHERE credit IS NOT NULL AND entry_id <= 3;

Result:

line_id (INT)account (INT)credit (DECIMAL(2))memo (STRING)
23000500000.00Owner puts money in the bank
42000257500.00Buy 500 CITY at 515
61100257500.00Pay the broker

(3 rows)

Example: = NULL finds nothing (sample data: query-app)

SELECT count(*) AS with_equals FROM app_users WHERE last_login = NULL;
SELECT count(*) AS with_is_null FROM app_users WHERE last_login IS NULL;

Result:

SELECT count(*) AS with_equals FROM app_users WHERE last_login = NULL

with_equals (LONG)
0

(1 row)

SELECT count(*) AS with_is_null FROM app_users WHERE last_login IS NULL

with_is_null (LONG)
2

(1 row)

Row comparison: (a, b) < (x, y)

Syntax:

(a, b, ...) = (x, y, ...)
(a, b, ...) != (x, y, ...)
(a, b, ...) < (x, y, ...)       also <=, >, >=

Compares lists of values in order, like words in a dictionary: first a with x; only when they are equal, b with y, and so on.

WrittenMeans
(a, b) < (x, y)a < x OR (a = x AND b < y)
(a, b) <= (x, y)a < x OR (a = x AND b <= y)
(a, b) = (x, y)a = x AND b = y
(a, b) != (x, y)a != x OR b != y

Example: Keyset paging of forum posts, newest first: page 1 (sample data: content-forum)

SELECT id, author, created_at FROM posts
ORDER BY created_at DESC, id DESC
LIMIT 3;

Result:

id (LONG)author (STRING)created_at (TIMESTAMP)
8gita2026-09-02T03:25:00.000Z
7hari2026-09-02T03:15:00.000Z
6sita2026-09-01T06:15:00.000Z

(3 rows)

Example: Page 2: the rows after the last row of page 1 (post 6) (sample data: content-forum)

SELECT id, author, created_at FROM posts
WHERE (created_at, id) < ('2026-09-01T12:00:00+05:45', 6)
ORDER BY created_at DESC, id DESC
LIMIT 3;

Result:

id (LONG)author (STRING)created_at (TIMESTAMP)
5ram2026-09-01T05:45:00.000Z
4gita2026-09-01T05:15:00.000Z
3hari2026-09-01T04:35:00.000Z

(3 rows)

Example: Daily candles after (CITY, 2026-09-08), in symbol and time order (sample data: sample)

SELECT symbol, time, close FROM ochl_day
WHERE (symbol, time) > ('CITY', '2026-09-08') AND time IN '2026-09'
ORDER BY symbol, time
LIMIT 4;

Result:

symbol (SYMBOL)time (TIMESTAMP)close (DOUBLE)
CITY2026-09-09T00:00:00.000Z504.6
CITY2026-09-10T00:00:00.000Z504.9
COAST2026-09-01T00:00:00.000Z367.4
COAST2026-09-02T00:00:00.000Z369.2

(4 rows)

Example (sample data: sample)

SELECT (2, 5) < (2, 7) AS a, (1, 'b') >= (1, 'a') AS b, (1, 2) = (1, 2) AS c, (1, NULL) = (1, 2) AS d;

Result:

a (BOOL)b (BOOL)c (BOOL)d (STRING)
truetruetrueNULL

(1 row)

Example: Both lists need the same number of values (sample data: sample; shows an error)

SELECT symbol FROM ochl_day WHERE (symbol, time) < ('CITY', '2026-09-08', 1);

Result:

Error: line 1, column 50: the two lists compared have 2 and 3 values; expected the same number

(a, b) IN a list of keys

Syntax:

(a, b, ...) [NOT] IN ((x1, y1, ...), (x2, y2, ...), ...)
(a, b, ...) [NOT] IN (VALUES (x1, y1, ...), (x2, y2, ...), ...)
(a, b, ...) [NOT] IN (SELECT x, y, ... FROM ...)

True when the values of the row are equal, all together, to one of the listed keys. (symbol, time) IN (('CITY', '2026-09-10'), ('COAST', '2026-09-09')) means (symbol = 'CITY' AND time = '2026-09-10') OR (symbol = 'COAST' AND time = '2026-09-09').

Example: Two daily candles picked by (symbol, day) (sample data: sample)

SELECT symbol, time, close FROM ochl_day
WHERE (symbol, time) IN (('CITY', '2026-09-10'), ('COAST', '2026-09-09'))
ORDER BY symbol;

Result:

symbol (SYMBOL)time (TIMESTAMP)close (DOUBLE)
CITY2026-09-10T00:00:00.000Z504.9
COAST2026-09-09T00:00:00.000Z380.1

(2 rows)

Example: The same keys from a VALUES list and from a table (sample data: sample)

Setup:

CREATE TABLE picks (symbol STRING, day TIMESTAMP);
INSERT INTO picks VALUES ('CITY', '2026-09-10'), ('COAST', '2026-09-09');
SELECT symbol, time, close FROM ochl_day WHERE (symbol, time) IN (VALUES ('CITY', '2026-09-10'), ('COAST', '2026-09-09')) ORDER BY symbol;
SELECT symbol, time, close FROM ochl_day WHERE (symbol, time) IN (SELECT symbol, day FROM picks) ORDER BY symbol;
EXPLAIN SELECT symbol, time, close FROM ochl_day WHERE (symbol, time) IN (('CITY', '2026-09-10'), ('COAST', '2026-09-09'));

Result:

SELECT symbol, time, close FROM ochl_day WHERE (symbol, time) IN (('CITY', '2026-09-10'), ('COAST',

symbol (SYMBOL)time (TIMESTAMP)close (DOUBLE)
CITY2026-09-10T00:00:00.000Z504.9
COAST2026-09-09T00:00:00.000Z380.1

(2 rows)

SELECT symbol, time, close FROM ochl_day WHERE (symbol, time) IN (SELECT symbol, day FROM picks) ORD

symbol (SYMBOL)time (TIMESTAMP)close (DOUBLE)
CITY2026-09-10T00:00:00.000Z504.9
COAST2026-09-09T00:00:00.000Z380.1

(2 rows)

EXPLAIN SELECT symbol, time, close FROM ochl_day WHERE (symbol, time) IN (('CITY', '2026-09-10'), ('

plan (STRING)
scan table ochl_day (1 of 2 partitions, about 57 rows)
read only 2 key range(s) of each partition
build 3 of 7 stored column(s) per row: time, symbol, close
filter while scanning: 1 condition(s)
engine: row (a lookup of whole keys: a binary search per partition)

(5 rows)

Example: Every key needs as many values as the list (sample data: sample; shows an error)

SELECT * FROM ochl_day WHERE (symbol, time) IN (('CITY'), ('COAST'));

Result:

Error: line 1, column 50: expected a list of 2 values like (1, 'x') for each item of (a, b) IN (...), got 'CITY'

NULL in WHERE

WHERE keeps a row only when the condition is true. When the condition is NULL (unknown), the row is left out, the same as false.

This surprises people with != and NOT IN: a row whose value is NULL is not "different from Kathmandu", it is unknown, so it is not shown.

Example: Users not in Kathmandu: the users without a city are missing (sample data: query-app)

SELECT username, city FROM app_users WHERE city != 'Kathmandu';

Result:

username (STRING)city (STRING)
sitaPokhara
gitaDharan
bikashLalitpur
sumanPokhara

(4 rows)

Example: Keep the NULL cities too (sample data: query-app)

SELECT username, city FROM app_users WHERE city != 'Kathmandu' OR city IS NULL;

Result:

username (STRING)city (STRING)
sitaPokhara
gitaDharan
bikashLalitpur
anitaNULL
sumanPokhara
test_userNULL

(6 rows)

Example: The same with coalesce (sample data: query-app)

SELECT username, coalesce(city, '(no city)') AS city FROM app_users WHERE coalesce(city, '') <> 'Kathmandu';

Result:

username (STRING)city (STRING)
sitaPokhara
gitaDharan
bikashLalitpur
anita(no city)
sumanPokhara
test_user(no city)

(6 rows)

Fast conditions

Syntax:

column op value [AND column op value ...]

KDB runs a query in one of two ways. The column engine reads whole columns at a time, on all threads. The row engine looks at one row after another. The column engine is many times faster. It takes a query only when every part of the WHERE (the parts joined by AND) has one of these shapes:

ShapeExample
a column against a value: =, !=, <, <=, >, >=close > 190, symbol = 'CITY', time >= '2026-09-01'
[NOT] BETWEEN two valuesclose BETWEEN 375 AND 380
[NOT] IN a list of valuessymbol IN ('CITY', 'COAST')
= or IN on one column, joined by ORsymbol = 'CITY' OR symbol = 'COAST' (the same as an IN list)
[NOT] LIKE, [NOT] ILIKE on a SYMBOL or STRING columnname LIKE '%Bank%', symbol ILIKE 'c%'
time [NOT] IN 'period'time IN '2026-09'
IS [NOT] NULLlast_login IS NULL
a column against a column of the same kindclose > open, buyer = seller
NOT, OR and AND of the shapes above, nested any wayNOT (volume <= 0 AND high = low AND open = close), buyer = 58 OR seller = 58
one member of a JSON column against textextra_data->>'category' = 'auth'

These send the whole query to the row engine. The result is the same, only slower:

ShapeExampleFaster way
a function around the columnupper(symbol) = 'CITY', abs(close) > 1symbol = 'CITY'
math on the columnclose * 2 > 400, close - open > 5close > 200; keep the column alone on one side
LIKE on a number or time columnquantity LIKE '1%'a range: quantity >= 100 AND quantity < 200
a STRING column against a number, or a JSON / BYTES column (other than ->> against text)code = 34 on a text column of numberscompare text with text: code = '34'
a column against a column of another kindquantity > rate (LONG and DOUBLE)

Example: Fast: every part is a column against a value (sample data: sample)

SELECT time, symbol, close
FROM ochl_day
WHERE (symbol = 'CITY' OR symbol = 'COAST') AND close > 400 AND time IN '2026-09'
ORDER BY time, symbol
LIMIT 4;

Result:

time (TIMESTAMP)symbol (SYMBOL)close (DOUBLE)
2026-09-01T00:00:00.000ZCITY502.4
2026-09-02T00:00:00.000ZCITY496.4
2026-09-03T00:00:00.000ZCITY497.9
2026-09-06T00:00:00.000ZCITY499

(4 rows)

Example: Fast too: a chart that leaves out empty bars with NOT ( ... AND ... ) (sample data: sample)

SELECT time, open, high, low, close, volume
FROM ochl_day
WHERE symbol = 'CITY' AND NOT (volume <= 0 AND high = low AND open = close)
ORDER BY time DESC
LIMIT 3;

Result:

time (TIMESTAMP)open (DOUBLE)high (DOUBLE)low (DOUBLE)close (DOUBLE)volume (DOUBLE)
2026-09-10T00:00:00.000Z504.6507500.7504.9183040
2026-09-09T00:00:00.000Z512512.4504504.6182390
2026-09-08T00:00:00.000Z498.2501.1497.5497.534400

(3 rows)

Example: Slow and fast ways to ask the same thing: the same rows either way (sample data: sample)

SELECT count(*) AS slow FROM ochl_day WHERE upper(symbol) = 'CITY' AND close * 2 > 1000;
SELECT count(*) AS fast FROM ochl_day WHERE symbol = 'CITY' AND close > 500;

Result:

SELECT count(*) AS slow FROM ochl_day WHERE upper(symbol) = 'CITY' AND close * 2 > 1000

slow (LONG)
14

(1 row)

SELECT count(*) AS fast FROM ochl_day WHERE symbol = 'CITY' AND close > 500

fast (LONG)
14

(1 row)