KDB knowledge

Operators

Every operator of KDB SQL, which one is done first, and what NULL does to each of them.

An operator is a sign or a word between values, like +, =, AND or ||. When an expression has many operators, KDB does the tight ones first: 2 + 3 * 4 is 14, because * binds tighter than +. Use ( ) to choose the order yourself, and to make long conditions easy to read.

Example: A trade log report line: value, commission and a flag (sample data: sample)

SELECT transaction_id, symbol,
       quantity * rate AS value,
       round(quantity * rate * 0.4 / 100, 2) AS commission,
       quantity >= 500 AND rate > 500 AS big_bank_trade
FROM trade_log
WHERE date IN '2026-09-10' AND symbol IN ('CITY', 'COAST')
ORDER BY value DESC
LIMIT 4;

Result:

transaction_id (LONG)symbol (SYMBOL)value (DOUBLE)commission (DOUBLE)big_bank_trade (BOOL)
100031COAST4020001608false
100015COAST3980001592false
100018CITY2580001032true
100023COAST199500798false

(4 rows)

Operator precedence

Syntax:

OR
AND
NOT
=  ==  !=  <>  <  <=  >  >=   IS [NOT] NULL   [NOT] IN   [NOT] BETWEEN   [NOT] LIKE   [NOT] ILIKE
+  -  ||
*  /  %
-value
::  ->  ->>

From loose (done last) to tight (done first):

LevelOperatorsExampleMeans
1ORa OR b AND ca OR (b AND c)
2ANDa AND b OR c(a AND b) OR c
3NOTNOT a = bNOT (a = b)
4= == != <> < <= > >=, IS [NOT] NULL, [NOT] IN, [NOT] BETWEEN, [NOT] LIKE, [NOT] ILIKEa + 1 = b * 2(a + 1) = (b * 2)
5+ - ||'x' || 1 + 2('x' || 1) + 2 (an error: write 'x' || (1 + 2))
6* / %2 + 3 * 42 + (3 * 4)
7- before a value-2 * 3(-2) * 3
8:: -> ->>, then values, columns, function calls, CASE, CAST, ( )'5'::INT * 2('5'::INT) * 2

Example (sample data: sample)

SELECT 2 + 3 * 4 AS a, (2 + 3) * 4 AS b, 10 - 4 - 3 AS c, -2 * 3 AS d,
       1 = 1 OR 1 = 2 AND 1 = 3 AS e, NOT 1 = 2 AND 2 = 2 AS f, 2 + 3 BETWEEN 4 AND 6 AS g;

Result:

a (LONG)b (LONG)c (LONG)d (LONG)e (BOOL)f (BOOL)g (BOOL)
14203-6truetruetrue

(1 row)

Example: Mixing AND and OR: ( ) change the answer (sample data: sample)

SELECT count(*) AS without_parens FROM trade_log WHERE symbol = 'TELCO' OR symbol = 'COAST' AND quantity >= 1000;
SELECT count(*) AS with_parens FROM trade_log WHERE (symbol = 'TELCO' OR symbol = 'COAST') AND quantity >= 1000;

Result:

SELECT count(*) AS without_parens FROM trade_log WHERE symbol = 'TELCO' OR symbol = 'COAST' AND quan

without_parens (LONG)
8

(1 row)

SELECT count(*) AS with_parens FROM trade_log WHERE (symbol = 'TELCO' OR symbol = 'COAST') AND quant

with_parens (LONG)
4

(1 row)

Example: || and + are on the same level (sample data: sample; shows an error)

SELECT 'Total: ' || 2 + 3 AS label;

Result:

Error: line 1, column 23: 'Total: 2' and '3' are not both numbers

Example: The fix: ( ) around the sum (sample data: sample)

SELECT 'Total: ' || (2 + 3) AS label;

Result:

label (STRING)
Total: 5

(1 row)

Arithmetic: + - * / %

Syntax:

a + b    a - b    a * b    a / b    a % b    -a

Add, subtract, multiply, divide and remainder. The full rules are in arithmetic operators.

OperatorResult
+ - *LONG for whole numbers (an INT result past INT becomes a LONG), else DOUBLE. A result too big for LONG is an error: "bigint out of range" (SQLSTATE 22003)
/DOUBLE: 7 / 2 is 3.5. After SET int_division = integer, whole number / whole number is a whole number, as in PostgreSQL: 7 / 2 is 3 (integer division)
%the remainder: 7 % 2 is 1
TIMESTAMP + numberthe time plus that many milliseconds
TIMESTAMP - TIMESTAMPthe milliseconds between the two times
DATE + numberthe date plus that many days

Example: Daily range and change in percent (sample data: sample)

SELECT symbol, round(high - low, 2) AS day_range, round((close - open) / open * 100, 2) AS change_pct
FROM ochl_day
WHERE time IN '2026-09-10';

Result:

symbol (SYMBOL)day_range (DOUBLE)change_pct (DOUBLE)
CITY6.30.06
COAST8.8-1.05
VOLT7.93.02

(3 rows)

Example: Nepal clock hour: add 5 h 45 min in milliseconds (sample data: sample)

SELECT hour(time + 20700000) AS npt_hour, sum(volume) AS volume
FROM ochl_minutes
WHERE symbol = 'CITY' AND time IN '2026-09-10'
GROUP BY hour(time + 20700000)
ORDER BY npt_hour;

Result:

npt_hour (LONG)volume (DOUBLE)
1147020
1241230
1346180
1448610

(4 rows)

Example (sample data: sample)

SELECT 7 / 2 AS a, 7 % 2 AS b, 5 / NULLIF(0, 0) AS c, 9223372036854775807 * 2.0 AS big_as_double, CAST('2026-09-10' AS DATE) + 1 AS next_day;

Result:

a (DOUBLE)b (LONG)c (STRING)big_as_double (DOUBLE)next_day (DATE)
3.51NULL1.84467440737096e+192026-09-11

(1 row)

Example: Dividing by zero is an error (sample data: sample; shows an error)

SELECT 5 / 0;

Result:

Error: line 1, column 10: division by zero

Example: + does not join text (sample data: sample; shows an error)

SELECT 'CITY' + 1;

Result:

Error: line 1, column 15: 'CITY' and '1' are not both numbers

Integer division: SET int_division

Syntax:

SET int_division = integer | float | default

In KDB, / gives a DOUBLE: 7 / 2 is 3.5. PostgreSQL gives 3: a whole number divided by a whole number stays a whole number there. An app written for PostgreSQL may count on that, for example to read sum(ms) / 1000 into an integer.

Setting7 / 2-7 / 27 / 2.0
float (the default)3.5-3.53.5
integer3-33.5

Example: The same division in both modes (sample data: sample)

SELECT 7 / 2 AS a, -7 / 2 AS b, 7 / 2.0 AS c;
SET int_division = integer;
SELECT 7 / 2 AS a, -7 / 2 AS b, 7 / 2.0 AS c, 7 / NULLIF(0, 0) AS d;

Result:

SELECT 7 / 2 AS a, -7 / 2 AS b, 7 / 2.0 AS c

a (DOUBLE)b (DOUBLE)c (DOUBLE)
3.5-3.53.5

(1 row)

SET int_division = 'integer'

whole number / whole number gives a whole number for this session (7 / 2 = 3)

SELECT 7 / 2 AS a, -7 / 2 AS b, 7 / 2.0 AS c, 7 / nullif(0, 0) AS d

a (LONG)b (LONG)c (DOUBLE)d (STRING)
3-33.5NULL

(1 row)

Example: An average in whole shares, as a PostgreSQL app expects (sample data: sample)

SET int_division = integer;
SELECT symbol, sum(quantity) / count(*) AS avg_whole_shares FROM trade_log WHERE date IN '2026-09-10' GROUP BY symbol ORDER BY symbol LIMIT 3;

Result:

SET int_division = 'integer'

whole number / whole number gives a whole number for this session (7 / 2 = 3)

SELECT symbol, sum(quantity) / count(*) AS avg_whole_shares FROM trade_log WHERE date IN '2026-09-10

symbol (SYMBOL)avg_whole_shares (LONG)
CITY130
COAST316
TELCO192

(3 rows)

Example: Only integer and float (sample data: sample; shows an error)

SET int_division = half;

Result:

Error: int_division must be integer or float, not 'half'

Comparison operators

Syntax:

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

Compare two values and give true or false. They are mostly used in WHERE, but a comparison is a normal value: you can put it in the select list.

Example: Up day or down day, as a true/false column (sample data: sample)

SELECT time, close, close > open AS up_day, close = high AS closed_at_high
FROM ochl_day
WHERE symbol = 'VOLT' AND time >= '2026-09-06';

Result:

time (TIMESTAMP)close (DOUBLE)up_day (BOOL)closed_at_high (BOOL)
2026-09-06T00:00:00.000Z180.1truefalse
2026-09-07T00:00:00.000Z176.9falsefalse
2026-09-08T00:00:00.000Z182.4truefalse
2026-09-09T00:00:00.000Z187.3truefalse
2026-09-10T00:00:00.000Z191.2truefalse

(5 rows)

Example: A BOOL cannot be compared with a number (sample data: sample; shows an error)

SELECT 1 = 1 = 1;

Result:

Error: line 1, column 14: cannot compare BOOL 'true' with LONG '1'

|| : join text

Syntax:

value || value

Joins two values into one text. Numbers, times and TRUE/FALSE are turned into text first.

Example: A label for a chart legend (sample data: sample)

SELECT symbol || ' - ' || name AS label FROM securities WHERE sector = 'Banking';

Result:

label (STRING)
CITY - City Bank Limited
COAST - Coastal Bank Limited

(2 rows)

Example: Full name with city; NULL makes the whole text NULL (sample data: query-app)

SELECT username, full_name || ' (' || city || ')' AS shown,
       full_name || ' (' || coalesce(city, 'no city') || ')' AS shown_safe
FROM app_users
WHERE id IN (1, 6, 8);

Result:

username (STRING)shown (STRING)shown_safe (STRING)
ramRam Sharma (Kathmandu)Ram Sharma (Kathmandu)
anitaNULLAnita Magar (no city)
test_userNULLNULL

(3 rows)

Example (sample data: sample)

SELECT 'CITY' || '-' || 1 AS a, 5.5 || '%' AS b, 'x' || NULL AS c;

Result:

a (STRING)b (STRING)c (STRING)
CITY-15.5%NULL

(1 row)

-> and ->> : parts of JSON

Syntax:

json -> 'key'      json -> index
json ->> 'key'     json ->> index

-> takes a part of a JSON value and gives it as JSON. ->> gives the part as text. An index counts from 0; -1 is the last element. The JSON functions are explained in JSON functions.

Example (sample data: sample)

SELECT '{"symbol": "CITY", "prices": [512, 515.5, 509]}' ->> 'symbol' AS symbol,
       '{"symbol": "CITY", "prices": [512, 515.5, 509]}' -> 'prices' AS prices,
       '{"symbol": "CITY", "prices": [512, 515.5, 509]}' -> 'prices' ->> -1 AS last_price;

Result:

symbol (STRING)prices (JSON)last_price (STRING)
CITY[512,515.5,509]509

(1 row)

:: : change the type

Syntax:

value::TYPE
value::DECIMAL(scale)

x::TYPE is the short form of CAST(x AS TYPE). See conversion functions for every type and rule.

Example (sample data: sample)

SELECT '42'::INT + 1 AS a, 512.45::DECIMAL(2) AS b, '2026-09-10T11:00:00+05:45'::TIMESTAMP AS c, 7::DOUBLE / 2 AS d;

Result:

a (LONG)b (DECIMAL(2))c (TIMESTAMP)d (DOUBLE)
43512.452026-09-10T05:15:00.000Z3.5

(1 row)

Example: Volume as a whole number (sample data: sample)

SELECT time, symbol, volume::LONG AS volume FROM ochl_day WHERE time IN '2026-09-10';

Result:

time (TIMESTAMP)symbol (SYMBOL)volume (LONG)
2026-09-10T00:00:00.000ZCITY183040
2026-09-10T00:00:00.000ZCOAST186200
2026-09-10T00:00:00.000ZVOLT41470

(3 rows)

Example: Not a number (sample data: sample; shows an error)

SELECT 'abc'::INT;

Result:

Error: line 1, column 13: cannot convert 'abc' to INT

NULL rules

NULL means "no value" or "unknown". Most operators give NULL when a value is NULL, because the answer is unknown too.

OperatorWith NULLExampleResult
+ - * / %NULLNULL + 1NULL
||NULL'a' || NULLNULL
= != < ...NULLNULL = NULLNULL
ANDfalse wins, else NULLFALSE AND NULLfalse
ORtrue wins, else NULLTRUE OR NULLtrue
NOTNULLNOT NULLNULL
IN (list)NULL when the value is NULL, or when it is not found and the list has a NULL2 IN (1, NULL)NULL
BETWEEN, LIKE, ILIKENULLNULL LIKE '%'NULL
IS NULL, IS NOT NULLnever NULLNULL IS NULLtrue
::, CASTNULLNULL::INTNULL

Example (sample data: sample)

SELECT NULL + 1 AS plus, NULL = NULL AS equals, FALSE AND NULL AS and_false, TRUE OR NULL AS or_true,
       NOT NULL AS not_null, NULL IS NULL AS is_null, coalesce(NULL, 0) + 1 AS with_default;

Result:

plus (STRING)equals (STRING)and_false (BOOL)or_true (BOOL)not_null (STRING)is_null (BOOL)with_default (LONG)
NULLNULLfalsetrueNULLtrue1

(1 row)

Example: Balance of each account: NULL debits and credits need coalesce (sample data: query-app)

SELECT account,
       sum(debit) - sum(credit) AS wrong_when_one_side_is_empty,
       coalesce(sum(debit), 0) - coalesce(sum(credit), 0) AS balance
FROM ledger
GROUP BY account
ORDER BY account;

Result:

account (INT)wrong_when_one_side_is_empty (DECIMAL(2))balance (DECIMAL(2))
10005000.005000.00
1100230320.00230320.00
1200247200.00247200.00
20000.000.00
3000NULL-500000.00
4000NULL-6250.00
4100NULL-2300.00
5000NULL1030.00
5100NULL25000.00

(9 rows)