KDB knowledge

11. SQL reference

What you will learn

This is a short reference. Chapters 3 to 5 explain everything with longer examples. In the syntax, [x] means "x is optional", a | b means "a or b", and ... means "more of the same".

The example results in this chapter are real. They come from examples/11-sql-reference/.

General rules

RuleDetail
StatementsEnd with ;. Many statements can be sent at once. Each one is saved on its own (statements between BEGIN and COMMIT, and a whole CALL, are saved together); KDB stops at the first error.
CaseKeywords, table names, column names and function names do not care about case. Text values do ('CITY' is not 'city').
NamesLetters, digits, _, and non-English letters. A reserved word or a special name must be in double quotes: "order".
TextIn single quotes: 'CITY'. A quote inside is doubled: 'City''s'.
Numbers42, -7, 512.45, 1.5e3. Integers from -9223372036854775808 to 9223372036854775807.
Other constantsTRUE, FALSE, NULL
Comments-- to the end of the line and /* ... */
ErrorsSay where: line 1, column 22: ...

Statements

Tables

CREATE TABLE [IF NOT EXISTS] name (
  column TYPE [PRIMARY KEY [AUTO]] [NOT NULL] [[CONSTRAINT name] UNIQUE] [DEFAULT expr]
              [[CONSTRAINT name] REFERENCES table[(column)] [ON DELETE action] [ON UPDATE action]]
              [ROUND interval] [AS (expr)] [[CONSTRAINT name] CHECK (condition)],
  ...
  [, [CONSTRAINT name] CHECK (condition) ...]
  [, [CONSTRAINT name] PRIMARY KEY (column, ...)]
  [, [CONSTRAINT name] UNIQUE (column, ...)]
  [, [CONSTRAINT name] FOREIGN KEY (column, ...) REFERENCES table (column, ...) [ON DELETE action] [ON UPDATE action]]
)
[TIMESERIES(time_column)]
[PARTITION BY DAY | MONTH | YEAR | NONE]
[SORT BY (column, ...)]
[DEDUP KEY (column, ...)]
[TTL n HOURS | DAYS | WEEKS | MONTHS | YEARS]
[RECORDS];

DROP TABLE [IF EXISTS] name;
TRUNCATE [TABLE] name [, name ...];      -- removes every row at once; not inside BEGIN ... COMMIT
RENAME TABLE old TO new;
ALTER TABLE name ADD [COLUMN] column TYPE [rules];
ALTER TABLE name DROP [COLUMN] column;
ALTER TABLE name RENAME [COLUMN] old TO new;
ALTER TABLE name DROP PARTITION 'key' [, 'key' ...];
ALTER TABLE name DROP PARTITION OLDER THAN n DAYS | MONTHS | YEARS | ...;
ALTER TABLE name SET TTL n DAYS | ... | NONE;
ALTER TABLE name DEDUP ENABLE UPSERT KEYS (column, ...);   -- or DEDUP DISABLE; rewrites the rows
ALTER TABLE name ADD [CONSTRAINT cname] CHECK (condition);
ALTER TABLE name DROP CONSTRAINT cname;
-- PostgreSQL forms (chapter 22):
CREATE TABLE name AS SELECT ...;
ALTER TABLE [IF EXISTS] name action [, action ...];
  -- ADD [COLUMN] [IF NOT EXISTS] ..., DROP [COLUMN] [IF EXISTS] c [CASCADE],
  -- ALTER [COLUMN] c {SET DEFAULT expr | DROP DEFAULT | SET NOT NULL | DROP NOT NULL | TYPE type [USING expr]},
  -- ADD [CONSTRAINT n] {UNIQUE (...) | PRIMARY KEY (...) | FOREIGN KEY (...) REFERENCES ... | CHECK (...)} [NOT VALID],
  -- DROP CONSTRAINT [IF EXISTS] n, RENAME [COLUMN] a TO b, RENAME TO new

SHOW TABLES;
SHOW COLUMNS FROM name;
SHOW CREATE TABLE name;
SHOW VIEWS;
PartRules
TIMESERIES(col)Makes a time-series table. col is TIMESTAMP or DATE.
PARTITION BYTime-series only. Default DAY. Partition keys: '2026', '2026-09', '2026-09-10' (UTC).
SORT BYTime-series only. Default: first SYMBOL column, then the time column.
DEDUP KEYTime-series only. Must include the time column.
TTL n DAYSTime-series only. Partitions whose last moment is older than that are dropped by themselves. SET TTL NONE stops it. DROP PARTITION OLDER THAN does the same once, now.
TRUNCATERemoves every row, and keeps the table, its columns and indexes. Much faster than DELETE FROM t. Returns 1 table(s) truncated (57 row(s) removed). ROLLBACK cannot undo it, so it is refused inside BEGIN ... COMMIT. Needs the write role (TRUNCATE ALL TABLES: admin).
RECORDSSays "relational table" explicitly. Cannot be used with TIMESERIES.
PRIMARY KEYAt most one per table. Implies NOT NULL.
AUTOInteger PRIMARY KEY only. Next value from the table's counter, which only goes up (never reuses ids of deleted or rolled-back rows).
DEFAULTA constant or now(). No column names.
AS (expr)Computed column, not stored. Uses stored columns of the same row only. No DEFAULT, keys or REFERENCES. Cannot be set by INSERT/UPDATE.
REFERENCES t(c)t.c must be PRIMARY KEY or UNIQUE and of the same kind of type. Checked on INSERT and UPDATE. Without ON DELETE, a referenced row cannot be deleted; a referenced table cannot be dropped. REFERENCES t means t's PRIMARY KEY.
ON DELETE / ON UPDATENO ACTION (default), RESTRICT, CASCADE, SET NULL, SET DEFAULT. All changes of a statement are one transaction. Chapter 22.
PRIMARY KEY (a, b)Rows are stored by the key.
PostgreSQL type namesSERIAL, VARCHAR(n), NUMERIC(p,s), UUID, TEXT[], ... See Types and chapter 22.
ROUND intervalTIMESTAMP only. Cuts values down: ROUND 1m, ROUND 1d.
CHECK (condition)Checked for every written row (INSERT, UPDATE, upserts, InsertRows, line protocol). A row where it is false is refused; NULL passes. Only stored columns of the same row and built-in functions. Name when not given: table_column_check / table_check. ADD CHECK refuses when a row already breaks it. Chapter 15.
ADD COLUMN ... NOT NULLNeeds a DEFAULT.
DROP COLUMNNot for: the TIMESERIES column, SORT BY / DEDUP KEY columns, the PRIMARY KEY, referenced columns, columns used by computed columns or indexes, the only column.

Writing

[WITH name AS (query), ...]
INSERT [OR REPLACE | OR IGNORE] INTO table [AS alias] [(column, ...)]
VALUES (value, ...) [, (value, ...) ...] | SELECT ... | DEFAULT VALUES
[ON CONFLICT [(column, ...) [WHERE ...] | ON CONSTRAINT name]
   DO NOTHING | DO UPDATE SET column = expr, ... [WHERE condition]]      -- chapter 23
[RETURNING expr [[AS] alias], ... | *];

UPDATE table [[AS] alias] SET column = expr [, column = expr ...] [FROM source, ...] [WHERE condition] [RETURNING ...];

DELETE FROM table [[AS] alias] [USING source, ...] [WHERE condition] [RETURNING ...];

Queries

[WITH [RECURSIVE] name [(column, ...)] AS (query), ...]
SELECT [DISTINCT | DISTINCT ON (expr, ...)] expr [[AS] alias], ... | * | table.*
[FROM table | view | (SELECT ...) [[AS] alias [(column, ...)]] | (VALUES (...), ...) AS alias (column, ...)
   | unnest(array) [AS alias (column)] | [LEFT JOIN] LATERAL (SELECT ...) alias
   [[INNER] JOIN | LEFT [OUTER] JOIN  source ON condition]
   [ASOF JOIN source [ON condition]]
   [CROSS JOIN source | , source] ...]
[WHERE condition]
[LATEST ON time_column PARTITION BY column, ...]
[SAMPLE BY interval [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 expr, ...]
[HAVING condition]
[UNION [ALL] | INTERSECT [ALL] | EXCEPT [ALL]  SELECT ... ...]
[ORDER BY expr [ASC | DESC] [NULLS FIRST | NULLS LAST], ...]
[LIMIT n | ALL] [OFFSET m [ROWS]] [FETCH FIRST n ROWS ONLY]
[FOR UPDATE];                            -- locks the tables it reads until the transaction ends (chapter 15)

EXPLAIN SELECT ...;
RuleDetail
Implicit GROUP BYWith aggregates or SAMPLE BY and no GROUP BY, every select item without an aggregate is a group key.
SAMPLE BYTime-series tables (or subqueries with a TIMESTAMP column). The output time is the bucket start.
SAMPLE BY intervalsms, s, m, h, d, w, M (month), y (year). m = minute, M = month.
WEEK STARTOnly with a week interval. Default Monday.
ALIGN TO FIRST OBSERVATIONNot with TIME ZONE, WITH OFFSET, months or years.
FILLApplies to all aggregate columns. Fills only between the first and last bucket with data. At most 10 million rows.
LATEST ONOne table, no joins. Runs after WHERE.
ASOF JOINBoth sides need a time column. Takes the last right row at or before the left row's time.
ORDER BYOutput column name, alias, position (ORDER BY 2), or an expression. Default NULL position: NULLS LAST for ASC, NULLS FIRST for DESC.
UNION, INTERSECT, EXCEPTSame number of columns. Without ALL the rows are distinct. INTERSECT goes before UNION and EXCEPT. ORDER BY / LIMIT at the end apply to all rows and must use output names or positions.
Subqueries(SELECT ...) as a value (must return 0 or 1 row), IN (SELECT ...), EXISTS (SELECT ...), FROM (SELECT ...), LATERAL. They may use outer columns (correlated); inner.col = outer.col becomes a hash lookup (chapter 23).
Aggregatesagg(x) FILTER (WHERE cond), array_agg(x ORDER BY y), GROUP BY 1 (a select column), aggregates in ORDER BY and HAVING.
Window functionsWith GROUP BY or SAMPLE BY they run over the groups, one row per group. Their arguments must be aggregates or group keys: sma(last(close), 2), rank() OVER (ORDER BY sum(volume) DESC). sma(close, 2) there is an error (column 'close' is used by a window function in a query with aggregates, so it must be a key of the groups). Not inside each other (lag(sma(close, 3))): use a subquery.
Big GROUP BYWhen the groups need more than half of --max-result-memory, KDB writes them to temporary files (_tmp/ in the database folder) and adds them up after the scan. The result is the same, only slower. At most 16 GB of such files per query.
SELECT without FROMAllowed: SELECT 1 + 1.

Views

CREATE [OR REPLACE] [CACHED] VIEW [IF NOT EXISTS] name AS [(] SELECT ... [)] [REFRESH ON COMMIT];
ALTER VIEW name AS SELECT ...;
REFRESH VIEW name;
DROP VIEW [IF EXISTS] name;
CREATE MATERIALIZED VIEW [IF NOT EXISTS] name [PARTITION BY DAY | MONTH | YEAR] [TTL n DAYS]
  AS SELECT time_bucket, ..., aggregates FROM table GROUP BY time_bucket, ...;
REFRESH MATERIALIZED VIEW name [FOR 'time' [TO 'time']];
DROP MATERIALIZED VIEW [IF EXISTS] name;
SHOW VIEWS;
KindWhat it keeps
ViewNothing. Its query runs every time you use it.
CACHED viewIts rows, in memory. Made at CREATE and at REFRESH VIEW; with REFRESH ON COMMIT again at the first read after a commit changed one of its tables. CREATE says view 'name' created (4 row(s) cached).
MATERIALIZED viewA real table with the result of a GROUP BY over one time-series table. After each commit, a background thread rebuilds only the partitions that changed. The GROUP BY must have a time bucket: the time column, to_date(time) or date_trunc('day', time).

REFRESH ON COMMIT needs CACHED. A view and a table cannot have the same name.

Chapter 17 explains them with a forum and a news example.

CREATE [UNIQUE | FULLTEXT] INDEX [IF NOT EXISTS] name ON table (column, ...);
CREATE [UNIQUE] INDEX [CONCURRENTLY] [IF NOT EXISTS] [name] ON [ONLY] table [USING method]
  ({column | (expression)} [DESC] [NULLS LAST], ...) [WHERE condition];   -- PostgreSQL form, chapter 22
DROP INDEX [IF EXISTS] name;
SHOW INDEXES [FROM table];   -- columns: name, table, kind, columns, built, rows, memory_bytes, create_sql
RuleDetail
NameUnique in the database.
Columns1 to 16 stored columns (not computed). Relational and time-series tables.
UNIQUETwo rows cannot have the same values. NULL values never clash. Error: duplicate value violates unique index 'name' on table 't': a row with col='value' already exists. Creating it fails when rows already clash.
FULLTEXTSTRING, SYMBOL or JSON columns. Used by MATCH(...), search_rank(...).
Where the data livesIn memory only. kdb serve builds it from the rows right after the database opens, on a background thread. In kdb sql, kdb shell and with --warm-indexes off, the first query that needs it builds it. CREATE INDEX builds it at once. Nothing on disk: crashes, kdb check, kdb repair, backups need nothing extra.
When a query uses an indexcol = value, col IN (...), col IS NULL on the first columns, then a range (<, <=, >, >=, BETWEEN, LIKE 'abc%', time IN '...', a row comparison) on the next column. ORDER BY the next columns (all ASC or all DESC) with LIMIT reads only the rows needed. MATCH uses a FULLTEXT index on the same columns. EXPLAIN shows use index name (...). Also used by UPDATE and DELETE.
When it does notA condition with OR, a function around the column, !=, NOT LIKE, a FLOAT column, text compared with a number, or when the index would read more than a quarter of the table.
DROP COLUMNRefused while an index uses the column. RENAME COLUMN / RENAME TABLE keep the index. DROP TABLE drops its indexes.
Expression and partial(lower(email)), WHERE is_default. A UNIQUE one is checked on every write. A non-unique one is used by a query that asks for the same thing: lower(email) = 'a@x.np', or a WHERE that has every part of the index's own WHERE.
Not in a transactionCREATE INDEX and DROP INDEX cannot be used between BEGIN and COMMIT.

Sequences

Chapter 22 explains them.

CREATE SEQUENCE [IF NOT EXISTS] name [INCREMENT [BY] n] [MINVALUE n] [MAXVALUE n] [START [WITH] n] [[NO] CYCLE];
ALTER SEQUENCE name [RESTART [WITH n]] [INCREMENT [BY] n] ...;
DROP SEQUENCE [IF EXISTS] name [, ...];
SELECT nextval('name'), currval('name'), setval('name', n [, is_called]), lastval();

A number is never given twice, also after a crash. currval and lastval are per session.

PostgreSQL schema statements

CREATE SCHEMA, CREATE EXTENSION (pgcrypto, uuid-ossp, citext, plpgsql, pg_trgm, btree_gin, btree_gist, unaccent) and COMMENT ON are accepted and change nothing. DO $$ BEGIN ... END $$ runs IF, RAISE and SQL statements. The views information_schema.tables, .columns, .table_constraints, .key_column_usage, ... and pg_class, pg_attribute, pg_indexes, pg_constraint, ... describe the database. Chapter 22.

Functions, procedures and triggers

Chapter 13 explains them with examples.

CREATE [OR REPLACE] FUNCTION name(param TYPE, ...) [RETURNS TYPE] AS (expression);
DROP FUNCTION [IF EXISTS] name;
SHOW FUNCTIONS;                         -- columns: name, arguments, returns, definition

CREATE [OR REPLACE] PROCEDURE name(param TYPE, ...) AS BEGIN
  statement; ...
END;
CALL name(argument, ...) [WITH DEBUG];
DROP PROCEDURE [IF EXISTS] name;
SHOW PROCEDURES;                        -- columns: name, arguments, definition

CREATE [OR REPLACE] TRIGGER name {BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table
  EXECUTE {CALL procedure(argument, ...) | INSERT ... | UPDATE ... | DELETE ... | BEGIN statement; ... END};
ALTER TABLE table {ENABLE | DISABLE} TRIGGER name;
DROP TRIGGER [IF EXISTS] name;
CREATE [OR REPLACE] TRIGGER name AFTER INSERT ON table DEFERRED [TIMEOUT 10min] EXECUTE ...;
RETRY TRIGGER QUEUE name;               -- a DEFERRED trigger: run the failed job again
SKIP TRIGGER JOB name job_id;           -- a DEFERRED trigger: leave one job out
SHOW TRIGGERS;                          -- columns: name, table, timing, event, enabled, definition

Statements inside a procedure or trigger body (each ends with ;):

SELECT ... | INSERT ... | UPDATE ... | DELETE ... | CALL name(...) | SHOW ... | EXPLAIN ...
DECLARE variable TYPE [DEFAULT expr]
SET variable = expr                     -- also: SET variable = (SELECT ...)
IF condition THEN ... [ELSIF condition THEN ...] [ELSE ...] END IF
WHILE condition DO ... END WHILE
FOR row IN (SELECT ...) DO ... END FOR  -- row.column is a value of the current row
DECLARE cursor CURSOR FOR SELECT ...
OPEN cursor
FETCH cursor INTO variable, ...        -- sets the variable FOUND
CLOSE cursor
RETURN
RAISE expr                              -- error: the whole CALL (or statement) is undone
PRINT expr                              -- a line in the "print" (or "debug") result
RuleDetail
Function bodyOne expression using only its parameters. No subqueries, aggregates or window functions. Not in computed columns. The name cannot be a built-in function.
Names in a bodyA column name wins over a variable or parameter with the same name.
CALLOne transaction. Its SELECTs return extra results; PRINT adds a print result; WITH DEBUG adds a debug result (step, routine, statement, rows, time_us, changes).
TriggersOnce per statement (also once per InsertRows call and POST /write request), in the same transaction. The body reads the tables NEW and OLD.
DEFERREDOnly AFTER INSERT. Runs after the commit, as a background job of the server; the insert does not wait. Chapter 13.
DROP TABLE, RENAME TABLERefused while the table has triggers.

Transactions and cursors

BEGIN [TRANSACTION | WORK];
COMMIT [TRANSACTION | WORK];
ROLLBACK [TRANSACTION | WORK];

DECLARE name CURSOR FOR SELECT ...;     -- runs the query and keeps the rows
FETCH [NEXT [n] | ALL] FROM name;       -- NEXT without n = 1 row
CLOSE name;
RuleDetail
ScopeA transaction and a cursor end with the text they were sent in (kdb sql, /exec, the web console). In a session (kdb shell, POST /session + X-KDB-Session, a PostgreSQL connection) they stay open over many requests. A text without a session that ends without COMMIT is rolled back with an error.
ErrorsA failing statement rolls back the whole open transaction. In a session, only ROLLBACK (or COMMIT, which then fails) is accepted after that.
Many writersWriters of different tables run at the same time; writers of the same table take turns: they wait up to 5 s (table 'x' is busy), and two transactions that would wait for each other get deadlock at once. Readers see the committed rows. An idle transaction is rolled back after 30 s. Chapter 15.
Not in a transactionCREATE, DROP, ALTER, RENAME, TRUNCATE, BACKUP DATABASE.
HTTPPOST /cursor, GET /cursor/<id>?fetch=N, DELETE /cursor/<id> (chapter 8).

Database

BACKUP DATABASE;          -- needs --backups <folder> on kdb serve / shell / sql
CHECK TABLE any_name;     -- reads every file of the whole database

CHECK TABLE checks all files; the table name is not used.

Databases

A database is a folder in the data folder (chapter 1). Each database has its own tables, views, triggers and procedures.

CREATE DATABASE [IF NOT EXISTS] name;
DROP DATABASE [IF EXISTS] name;     -- closes it and moves its folder to <data folder>/_trash
SHOW DATABASES;                     -- columns: name, tables, bytes, current
USE name;                           -- the statements after it use this database
SELECT ... FROM other_db.table ...; -- read a table of another database on the same server
RuleDetail
NamesLetters, digits, _ and -, 1 to 64 characters. Market and market are the same database. The folder keeps the name as you first wrote it. Not allowed: _trash and the Windows device names CON, PRN, AUX, NUL, COM0-COM9, LPT0-LPT9.
DROP DATABASERefused while another request or shell uses the database. Nothing is deleted: the folder moves to _trash/<time in ms>-<name>. To get it back, stop the server and move the folder back.
database.tableReads a table of another database: SELECT, joins, subqueries, INSERT INTO t SELECT ..., CREATE TABLE t AS SELECT .... A statement only changes the database it runs in. Works over HTTP, the PostgreSQL port, kdb shell and the console.
USEIn kdb shell: stays until the next USE. Over HTTP: only for the rest of that request; choose the database of a request with db=<name> (chapter 8).
Where they workkdb shell and kdb sql with a database name (or no name), and kdb serve with a data folder. A command that opened one folder by its path (kdb serve D:\kdb\market) cannot create or drop databases.
@"
CREATE DATABASE IF NOT EXISTS market;
CREATE DATABASE demo;
USE demo;
CREATE TABLE notes (id INT PRIMARY KEY, text STRING);
SHOW DATABASES;
USE market;
DROP DATABASE demo;
DROP DATABASE IF EXISTS demo;
SHOW DATABASES;
"@ | kdb shell --data-dir kdb-data
KDB 0.2.0-dev shell on default (data folder kdb-data)
End statements with ';'. Type .help for commands, .exit to quit.
kdb> database 'market' created
kdb> database 'demo' created
kdb> using database 'demo'
kdb> table 'notes' created
kdb> name    | tables | bytes | current
--------+--------+-------+--------
default |      0 |     0 | false
demo    |      1 |   229 | true
market  |      0 |     0 | false
(3 rows, 0.1 ms)
kdb> using database 'market'
kdb> database 'demo' dropped; its folder was moved to kdb-data/_trash/1789135960212-demo
kdb> database 'demo' does not exist
kdb> name    | tables | bytes | current
--------+--------+-------+--------
default |      0 |     0 | false
market  |      0 |     0 | true
(2 rows, 0.0 ms)
kdb>

Users and roles

Users of the KDB server (chapter 16). They belong to the data folder, not to one database.

CREATE USER [IF NOT EXISTS] name [WITH] PASSWORD 'secret' [ROLE admin|write|read|none];
ALTER USER name [WITH] PASSWORD 'new secret';
ALTER USER name ROLE read;
DROP USER [IF EXISTS] name;
SHOW USERS;                                              -- columns: name, role, grants, created, changed
GRANT read|write|admin|ALL ON DATABASE db TO name;       -- ALL = admin
REVOKE [read|write|admin|ALL] ON DATABASE db FROM name;
RoleStatements
readSELECT, SHOW, EXPLAIN, BEGIN/COMMIT/ROLLBACK, DECLARE/FETCH/CLOSE
write+ INSERT, UPDATE, DELETE (also with RETURNING), TRUNCATE, CALL, SELECT ... FOR UPDATE
admin+ CREATE/ALTER/DROP/RENAME of tables, views, indexes, functions, procedures, triggers; BACKUP DATABASE; CHECK TABLE
ROLE admin (own role)+ CREATE/DROP DATABASE, all user statements
RuleDetail
Namesletters, digits, _ and -, 1 to 63 characters; not case sensitive
Passwords8 to 1024 bytes. Shown as '***' in every statement KDB shows back.
First usermust have ROLE admin. From then on the server asks for a login.
Last admincannot be dropped or lose ROLE admin while other users exist
Own passwordevery user may run ALTER USER <own name> WITH PASSWORD '...'
SHOW USERSnever shows passwords or hashes; a user who is not an admin sees only their own row
kdb shell, kdb sqlopen the files directly and do not check users

Types

TypeAlso writtenSizeRangeNULL stored as
BOOLBOOLEAN1 byteTRUE, FALSEspecial value
BYTE1 byte-127 to 127-128
SHORT2 bytes-32767 to 32767-32768
INTINTEGER4 bytes-2147483647 to 2147483647smallest value
LONGBIGINT8 bytes-9223372036854775807 to 9223372036854775807smallest value
FLOAT4 bytesabout ±3.4e38, 7 digitsNaN
DOUBLE8 bytesabout ±1.8e308, 15 digitsNaN
DECIMAL(s)8 bytesexact; value × 10^s must fit in LONG; s = 0..18smallest value
DATE4 bytesdays since 1970-01-01smallest value
TIMESTAMP8 bytesmilliseconds since 1970-01-01 UTCsmallest value
SYMBOL4 bytesrepeated textspecial value
STRINGVARCHAR, TEXTlength + textUTF-8 text, at most 16 MBspecial length
JSONJSONBlength + textchecked JSON, stored without extra spaces, at most 16 MBspecial length
BYTESBYTEA, BLOBlength + bytesany bytes (also zero bytes), at most 16 MB; written X'0A0B' or '\x0a0b', shown as \x0a0bspecial length

Chapter 18 explains JSON and BYTES with examples.

PostgreSQL type names are accepted too, with their rules: SMALLINT, SERIAL/BIGSERIAL (the PRIMARY KEY with AUTO), REAL, DOUBLE PRECISION, NUMERIC(p,s) (DECIMAL(s), p digits checked), NUMERIC (DOUBLE), MONEY, VARCHAR(n)/CHAR(n) (STRING, at most n characters), TIMESTAMPTZ (TIMESTAMP), TIME (STRING HH:MM:SS), INTERVAL (LONG milliseconds), UUID (STRING, checked), INET, CITEXT, type[] (JSON array). The full table is in chapter 22.

Conversions (when a value goes into a column, or with CAST)

IntoAccepts
integerswhole numbers, whole DOUBLE values (5.0), text like '42', TRUE/FALSE. Out of range → error.
DOUBLE, FLOATnumbers, text like '512.5'
DECIMAL(s)numbers and text with at most s digits after the point. More digits → error (use round(x, s)).
TIMESTAMPtime text (see time rules), DATE, a number (milliseconds)
DATE'2026-09-10', time text (the UTC date is kept), TIMESTAMP
BOOLTRUE/FALSE, 1/0, 'true'/'false'
STRING, SYMBOLanything (written as text)
JSONJSON text (checked; bad JSON → error with the position), numbers, TRUE/FALSE
BYTESX'0A0B', text '\x0a0b' (hex), other text (its UTF-8 bytes)

CAST(x AS TYPE) and x::TYPE are the same. CAST(x AS DECIMAL(2)) needs the scale.

Operators

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

LevelOperators
1OR
2AND
3NOT
4= == != <> < <= > >=, IS [NOT] NULL, [NOT] IN, [NOT] BETWEEN, [NOT] LIKE, [NOT] ILIKE
5+ - ||
6* / %
7unary -
8:: cast, -> ->> (JSON), ( ), values, columns, function calls, CASE, CAST
SELECT 2 + 3 * 4 AS a, (2 + 3) * 4 AS b, -2 * 3 AS c, 1 = 1 OR 1 = 2 AND 1 = 3 AS d, NOT 1 = 2 AND 2 = 2 AS e;
a,b,c,d,e
14,20,-6,true,true
Operator ruleExampleResult
/ always gives a decimal number7 / 23.5
Division or % by zero gives NULL5 / 0NULL
Integer overflow switches to DOUBLE
TIMESTAMP ± number = milliseconds addedCAST('2026-09-10T11:00:00+05:45' AS TIMESTAMP) + 600002026-09-10T05:16:00.000Z
TIMESTAMP - TIMESTAMP = milliseconds'2026-09-10T11:05:00Z'::TIMESTAMP - '2026-09-10T11:00:00Z'::TIMESTAMP300000
DATE ± number = days added
Text compared with TIMESTAMP/DATE is read as a timetime >= '2026-09-01'
Text is joined with || (not +); NULL on either side gives NULL'CITY' || '-' || 1CITY-1
LIKE: % any text, _ one character (also one letter of any language), \ before % or _ means the letter itself, case matters'COAST' LIKE 'C_AST'true
ILIKE: like LIKE, but English letters match in any case'City' ILIKE 'CIT%'true
json -> 'key', json -> 0: a part of a JSON value (as JSON); ->> gives it as text'{"a": [5]}' -> 'a' ->> 05
Row comparison: (a, b) < (x, y) means a < x OR (a = x AND b < y); also <= > >= = !=, any number of values(2, 5) < (2, 7)true

NULL and logic

SELECT coalesce(NULL, 'n/a') AS coalesce, nullif(5, 5) AS nullif, NULL + 1 AS null_plus, NULL = NULL AS null_eq,
       NULL IS NULL AS is_null, TRUE AND NULL AS and_null, TRUE OR NULL AS or_null, NOT FALSE AS not_false,
       3 BETWEEN 1 AND 5 AS between_1_5, 'COAST' IN ('CITY', 'COAST') AS in_list, 1 <> 2 AS not_equal;
coalesce,nullif,null_plus,null_eq,is_null,and_null,or_null,not_false,between_1_5,in_list,not_equal
n/a,,,,true,,true,true,true,true,true

(Empty CSV fields are NULL.) NULL in arithmetic or comparison gives NULL. WHERE and HAVING treat NULL as false. Use IS NULL.

Functions

Math

FunctionMeaningExampleResult
abs(x)absolute valueabs(-3.5)3.5
round(x), round(x, d)round (half away from zero) to d decimalsround(512.456, 2), round(512.5)512.46, 513
floor(x)round downfloor(5.7)5
ceil(x), ceiling(x)round upceil(5.2)6
sqrt(x)square root (NULL for negative)sqrt(16)4
pow(x, y), power(x, y)x to the power ypow(2, 10)1024
exp(x)e to the power xexp(0)1
ln(x), log(x)natural log (NULL for ≤ 0)ln(1)0
log(base, x)log with a baselog(2, 8)3
log10(x)log base 10log10(1000)3
sign(x)-1, 0 or 1sign(-7)-1
mod(x, y)remainder (same as %)mod(10, 3)1
greatest(a, b, ...)biggest (NULLs ignored)greatest(3, 9, 4)9
least(a, b, ...)smallest (NULLs ignored)least(3, 9, 4)3

More math, finance and price functions (trunc, cbrt, log2, gcd, lcm, factorial, pi, trigonometry, width_bucket, clamp, safe_divide, div0, percent, random, setseed, normal_cdf, normal_inv, round_tick, pivot_point, pivot_level, cagr, fv, pv, pmt, nper, npv, irr) are in chapter 19.

Text

FunctionMeaningExampleResult

Text functions count characters, so text in any language works (length('नेपाल') is 5). A NULL argument gives NULL (except concat, concat_ws, coalesce).

FunctionMeaningExampleResult
upper(s)upper case (English letters; other letters stay)upper('city')CITY
lower(s)lower caselower('CITY')city
length(s), char_length(s)number of characters (BYTES: number of bytes)length('नेपाल')5
octet_length(s)number of bytesoctet_length('नेपाल')15
substr(s, start [, len]), substring(...)part of the text; start counts from 1substr('CITY-1', 1, 4)CITY
left(s, n), right(s, n)first / last n characters (negative n: all but n)left('CITY', 2)CI
trim(s [, chars]), ltrim, rtrim, btrimremove spaces (or the given characters) at the endstrim('xxNICAxx', 'x')COAST
replace(s, from, to)replace allreplace('CITY-1', '-1', '')CITY
split_part(s, sep, n)field n (1 = first, -1 = last)split_part('2082/83', '/', 2)83
strpos(s, part), position(part IN s)where part starts (1 = first character), 0 = not foundstrpos('CITY', 'TY')3
starts_with(s, p), ends_with(s, p)true if s starts / ends with pstarts_with('CITY-1', 'CIT')true
lpad(s, n [, fill]), rpad(...)fill to n characters (cut if longer)lpad('7', 3, '0')007
repeat(s, n), reverse(s)repeat n times; characters backwardsrepeat('ab', 2)abab
concat(a, b, ...)join as text (NULLs skipped)concat('CITY', '-', 1)CITY-1
concat_ws(sep, a, b, ...)join with a separator (NULLs skipped)concat_ws(', ', 'a', NULL, 'b')a, b
regexp_like(s, pattern [, 'i'])true when the regular expression matches ('i': any case)regexp_like('CITY', '^C[A-Z]+$')true
regexp_substr(s, pattern [, 'i'])the first match, or NULLregexp_substr('EPS 12.50', '\d+\.\d+')12.50
regexp_count(s, pattern [, 'i'])number of matchesregexp_count('a1b22', '\d+')2
regexp_replace(s, pattern, new [, flags])replace the first match; flag g = every match; \1 in new = group 1regexp_replace('a1b2', '\d', '#', 'g')a#b#
coalesce(a, b, ...)first value that is not NULLcoalesce(NULL, 'n/a')n/a
nullif(a, b)NULL if a = b, else anullif(5, 5)NULL

Regular expressions: . [abc] [^abc] [a-z] \d \w \s \b ^ $ (...) (?:...) a|b * + ? {n,m} (and lazy *?). \w also matches letters of other languages. Back references and look-ahead are not supported. KDB never backtracks, so no pattern can hang the server.

JSON and BYTES

Chapter 18 has examples. A path looks like '$.a.b[0]', '$.items[-1]' (last) or '$."key with spaces"'.

FunctionMeaning
json(text), CAST(text AS JSON)check text and make it JSON
json_valid(text)true when the text is valid JSON
json_extract(j, path)the part at path, as JSON (NULL if missing)
json_value(j, path)the part as a value: text, LONG, DOUBLE or BOOL (NULL for objects, arrays, null)
json_value_text / json_value_double / json_value_long / json_value_bool(j, path)the part as that type (NULL if it does not fit)
json_type(j [, path])object, array, string, number, boolean, null
json_array_length(j [, path]), json_keys(j [, path])number of elements; the keys as a JSON array
json_object(k, v, ...), json_array(v, ...), to_json(v)build JSON from values
json_set(j, path, v, ...), json_insert, json_replace, json_remove(j, path, ...), json_array_append(j, path, v)change JSON (for UPDATE)
hex(x), unhex(text)bytes to hex text and back
to_base64(x), from_base64(text), encode(bytes, 'hex'|'base64'), decode(text, 'hex'|'base64')base64 and hex

Chapter 17 has examples. They work with or without a FULLTEXT index (without one, every row is read).

FunctionMeaning
MATCH(col, ..., 'words')true when the text of the columns has every word. '"exact phrase"' = words next to each other; 'hydro*' = a word that starts with hydro; '-loan' = rows with this word are left out
search_rank(col, ..., 'words')how well the row matches (BM25; higher is better, 0 when it does not match)
highlight(col, ..., 'words')the text with the found words in <b> and </b>

Words: letters and digits of any language (English, Nepali). English letters are made lower case, Devanagari digits become 0-9, punctuation (also । and ॥) separates words, and a number like 12.5 stays one word.

Time

All of these work in UTC. Arguments can be TIMESTAMP, DATE or time text.

FunctionMeaningExampleResult
now(), systimestamp()current time (the same for the whole statement)now()2026-09-11T08:45:59.939Z
year(t) month(t) day(t)parts of the UTC dateyear('2026-09-10T08:30:15Z')2026
hour(t) minute(t) second(t)parts of the UTC timehour('2026-09-10T08:30:15Z')8
day_of_week(t)0 = Sunday ... 6 = Saturdayday_of_week('2026-09-11')5
is_trading_day(t)true for Sunday to Thursday (UTC date; holidays are not known)is_trading_day('2026-09-11')false
date_trunc(unit, t)cut down to year, quarter, month, week (Sunday), day, hour, minute, second, msdate_trunc('week', '2026-09-10T08:30:00Z')2026-09-06T00:00:00.000Z
extract(field FROM t), date_part('field', t)year, quarter, month, week, day, dow, isodow, doy, hour, minute, second, epoch, ...extract(dow FROM '2026-09-11')5
dateadd(unit, n, t)add n units: y, month, w, d, h, m, s, ms (day clamped at month end)dateadd('month', 1, '2026-01-31')2026-02-28T00:00:00.000Z
datediff(unit, a, b)whole units from a to b: y, month, w, d, h, m, s, ms (months and years by the calendar)datediff('m', '2026-09-10T11:00:00+05:45', '2026-09-10T15:00:00+05:45')240
to_timestamp(x)text or number (ms) to TIMESTAMPto_timestamp('2026-09-10T11:00:00+05:45')2026-09-10T05:15:00.000Z
to_date(x)to DATE (UTC date)to_date('2026-09-10T20:00:00Z')2026-09-10
to_char(t, format [, zone])time as text: YYYY YY MM Mon Month DD DDD Dy Day D HH24 HH12 MI SS MS AM Q (quarter), "text"; zone like '+05:45' or 'Asia/Kathmandu'to_char('2026-09-10T08:30:00Z', 'YYYY-MM-DD HH24:MI', '+05:45')2026-09-10 14:15

Nepal clock: hour(time + 20700000) (20700000 ms = 5 h 45 min).

Also in chapter 19: age, quarter, day_of_year, week_of_year, last_day, make_date, make_timestamp, fiscal_year, fiscal_quarter, nepal_fiscal_year.

Aggregates

Used in SELECT with GROUP BY, SAMPLE BY or alone; also with OVER (...).

FunctionMeaning
count(*)number of rows
count(x)rows where x is not NULL
count(DISTINCT x), count_distinct(x)number of different values
sum(x)total (stays integer or DECIMAL when the inputs are)
avg(x)average
min(x), max(x)smallest, biggest (numbers, text, times)
first(x), last(x)value of the earliest / latest row by time
median(x)middle value
percentile(x, p)p = 0.9 or 90
stddev(x), stddev_samp(x)sample standard deviation
stddev_pop(x)population standard deviation
variance(x), var_samp(x), var_pop(x)variance
vwap(price, volume)sum(price × volume) / sum(volume)
SELECT count(*) AS count_all, count(close) AS count_close, count(DISTINCT symbol) AS count_distinct,
       count_distinct(symbol) AS count_distinct2, sum(volume) AS sum, round(avg(close), 3) AS avg,
       min(close) AS min, max(close) AS max, first(close) AS first, last(close) AS last
FROM ochl_day WHERE time IN '2026-09-10';
SELECT round(stddev(close), 3) AS stddev, round(stddev_pop(close), 3) AS stddev_pop, round(variance(close), 3) AS variance,
       round(var_pop(close), 3) AS var_pop, median(close) AS median, percentile(close, 0.9) AS p90,
       round(vwap(close, volume), 3) AS vwap
FROM ochl_day WHERE time IN '2026-09-10';
count_all,count_close,count_distinct,count_distinct2,sum,avg,min,max,first,last
3,3,3,3,410710,357.4,191.2,504.9,504.9,191.2
stddev,stddev_pop,variance,var_pop,median,p90,vwap
157.684,128.748,24864.19,16576.127,376.1,479.14,414.832

Aggregates ignore NULL values. count() without * is the same as count(*).

More aggregates are in chapter 19 (row engine only):

GroupFunctions
Correlation, regressioncorr, covar_pop, covar_samp, regr_slope, regr_intercept, regr_r2, regr_count, regr_avgx, regr_avgy, regr_sxx, regr_syy, regr_sxy, beta, alpha
Shapeskewness (skew), skewness_pop, kurtosis (kurt), kurtosis_pop, geomean, harmonic_mean, mode, percentile_cont, percentile_disc (also ... WITHIN GROUP (ORDER BY x)), median_abs_deviation (mad), iqr, range, entropy
Conditions, picking rowscount_if, sum_if, avg_if, bool_and (every), bool_or, arg_min (min_by), arg_max (max_by), approx_count_distinct, sum_of_squares, product, weighted_avg, twap
Listsstring_agg, listagg, array_agg (json_agg)
Risk, moneysharpe_ratio, sortino_ratio, calmar_ratio, information_ratio, max_drawdown, value_at_risk, expected_shortfall, xnpv, xirr

Series and window functions

Used in SELECT only. Without OVER, series functions run over all result rows in time order.

FunctionMeaning
sma(x, n)simple moving average of n rows
ema(x, n)exponential moving average (starts with SMA of the first n rows)
rsi(x, n)Wilder's RSI (0-100)
atr(high, low, close, n)Wilder's average true range
lag(x [, k [, default]])value k rows before (default 1); default instead of NULL when there is no such row
lead(x [, k [, default]])value k rows after
pct_change(x)(x - previous) / previous × 100
cumsum(x)running total
row_number()1, 2, 3, ...
rank()rank with gaps (1, 1, 3)
dense_rank()rank without gaps (1, 1, 2)
any aggregate OVER (...)aggregate over a window
fn(...) OVER ([PARTITION BY expr, ...] [ORDER BY expr [ASC|DESC], ...] [frame])

frame:  ROWS n PRECEDING | ROWS UNBOUNDED PRECEDING
      | {ROWS | RANGE} BETWEEN start AND end
start, end:  UNBOUNDED PRECEDING | n PRECEDING | CURRENT ROW | n FOLLOWING | UNBOUNDED FOLLOWING
             (RANGE: n is a number, or text like '5 days' for a time ORDER BY)

The period n must be a whole number, 1 or more.

More window and series functions are in chapter 19: percent_rank, cume_dist, ntile, first_value, last_value, nth_value, cumprod, cummax, cummin, returns and risk (pct_return, log_return, cum_return, drawdown, volatility, zscore, minmax_scale) and technical indicators (wma, dema, tema, trima, kama, hma, vwma, macd, macd_signal, macd_hist, stoch_k, stoch_d, stochf_k, stochrsi_k, stochrsi_d, willr, cci, roc, rocp, rocr, mom, trix, natr, trange, bb_upper, bb_middle, bb_lower, bb_percent_b, bb_width, kc_upper, kc_middle, kc_lower, dc_upper, dc_middle, dc_lower, plus_di, minus_di, dx, adx, adxr, aroon_up, aroon_down, aroon_osc, sar, ichimoku_tenkan, ichimoku_kijun, ichimoku_senkou_a, ichimoku_senkou_b, ichimoku_chikou, obv, ad, cmf, adosc, mfi, efi, eom, supertrend, supertrend_direction, ha_open, ha_high, ha_low, ha_close, linearreg, linearreg_slope, linearreg_intercept, linearreg_angle, tsf).

Table functions and PIVOT

SELECT ... FROM generate_series(start, stop [, step]) [[AS] alias [(column)]]   -- numbers or times
SELECT ... FROM long_sequence(n)                                              -- column x = 1 .. n
SELECT ... FROM source PIVOT (agg(x) [AS name], ... FOR column IN (value [AS name], ...)) [[AS] alias]

Chapter 19 explains them (Part 6).

Passwords, hashes and random values

FunctionMeaningExampleResult
password_hash(s)slow, salted hash for storing a password (PBKDF2-HMAC-SHA-256, 600,000 rounds)password_hash('secret')$pbkdf2-sha256$i=600000$...
password_hash(s, n)the same with n rounds (1,000 to 10,000,000)password_hash('secret', 1000)$pbkdf2-sha256$i=1000$...
password_verify(s, hash)does the password match the hash? (false for a damaged hash)password_verify('secret', password_hash)true
sha256(s)SHA-256 as 64 hex characterssha256('abc')ba7816bf...f20015ad
hmac_sha256(key, s)HMAC-SHA-256 as hexhmac_sha256('key', 'message')6e9ef29b...
random_bytes(n)n random bytes (1 to 1024) as hexrandom_bytes(8)6614740117d2ce39
random_token([n])n random bytes (default 32) as URL-safe base64random_token()43 characters
gen_random_uuid()random UUID (version 4)gen_random_uuid()0465d47f-8194-4c94-a372-eda3b70504a0

A NULL argument gives NULL. The random functions give a new value for every row and every call. password_hash and password_verify take about 0.05 to 0.4 seconds each: call password_verify once per login. password_hash('...') with a literal password in INSERT ... VALUES and UPDATE ... SET is hashed before the statement waits for the database, and the statement shown back contains the hash, not the password. Chapter 16 shows the register and login pattern.

Other expressions

ExpressionExample
CASE WHEN cond THEN a [WHEN ...] [ELSE b] ENDCASE WHEN pct_change > 0 THEN 'up' ELSE 'down' END
CASE x WHEN v THEN a ... ENDCASE symbol WHEN 'CITY' THEN 1 ELSE 0 END
CAST(x AS TYPE), x::TYPECAST('512.5' AS DOUBLE), '42'::INT
x [NOT] IN (a, b, ...)symbol IN ('CITY', 'COAST')
t [NOT] IN 'interval'time IN '2026-09'
x [NOT] BETWEEN a AND bclose BETWEEN 500 AND 510
EXISTS (SELECT ...)
(SELECT ...)a single value

Time rules

RuleDetail
StorageTIMESTAMP = milliseconds since 1970-01-01 UTC. DATE = days.
OutputAlways UTC: 2026-09-10T05:15:00.000Z, DATE as 2026-09-10.
Time textYYYY-MM-DD, then optional T or space and HH, :MM, :SS, .fff, then optional Z or +HH:MM / +HHMM / -HH:MM.
No zoneUTC. Nepal time must have +05:45.
More than 3 fraction digitscut to milliseconds
Numbersmilliseconds since 1970 (UTC)
time IN 'text'The whole year / month / day / hour / minute / second of the text, in UTC.
Partitionsby UTC date
SAMPLE BY ... TIME ZONEthe only place where KDB computes in another time zone
--until of kdb restorelocal time of the computer
Backup folder namesUTC

Time zones for TIME ZONE (fixed offsets only, no daylight saving): UTC, GMT, Z, Etc/UTC, Asia/Kathmandu, Asia/Katmandu, NPT, Asia/Kolkata, Asia/Calcutta, IST, Asia/Colombo, Asia/Dhaka, Asia/Thimphu, Asia/Karachi, Asia/Dubai, Asia/Bangkok, Asia/Jakarta, Asia/Shanghai, Asia/Hong_Kong, Asia/Singapore, Asia/Taipei, Asia/Tokyo, Asia/Seoul, or an offset like '+05:45'. Others are refused:

SELECT time, count(*) FROM ochl_minutes SAMPLE BY 1h TIME ZONE 'Europe/London';
error: time zone 'Europe/London' is not supported (it may change for daylight saving); use a fixed offset like '+05:45'

Reserved words

These words cannot be table, column or alias names without double quotes:

ALTER AND AS BETWEEN CASE CREATE DEFAULT DELETE DISTINCT DROP ELSE END FALSE FROM GROUP IN INSERT INTO IS JOIN LIKE LIMIT NOT NULL ON OR ORDER PRIMARY REFERENCES SELECT SET TABLE THEN TO TRUE UNIQUE UPDATE VALUES WHEN WHERE

CREATE TABLE orders (id INT, order STRING);
error: line 1, column 30: expected a column name but found the reserved word 'order' (write it in double quotes, "order", to use it as a name)
CREATE TABLE orders (id INT, "order" STRING);
INSERT INTO orders VALUES (1, 'buy');
SELECT id, "order" FROM orders;
table 'orders' created

1 row(s) inserted

id | order
---+------
 1 | buy  
(1 row, 0.1 ms)

These words can be names, but not a short alias without AS (write AS left then): ASOF CROSS FULL HAVING INNER LATEST LEFT LT OVER REFRESH RIGHT SAMPLE SPLICE UNION USING.

All other words (time, date, timestamp, symbol, partition, open, close, volume, value, key, ...) are normal names.

Limits

LimitValue
DECIMAL scale0 to 18
Nesting of expressions100 levels
Nesting of subqueries and views16 levels
Nesting of saved functions, of CALL, of triggers16 levels each
Rows changed in one transaction (BEGIN/COMMIT, CALL, statement with triggers)10 million
Rounds of one WHILE loop10 million
Open cursors16 per database; unused ones close after 10 minutes; 10 million rows each
Tables in one query63
Rows made by FILL10 million
Rows returned by kdb sql1000 in table format (--max-rows), no limit in csv/json
Rows returned by HTTP /exec10,000 by default, 1,000,000 at most
HTTP request body64 MB
One STRING, JSON or BYTES value16 MB (keep bigger files outside the database)
JSON nesting256 levels
Regular expression4096 bytes; {n,m} up to 1000
Writers at the same timeone per table; writers of different tables run together
Readers at the same timeany number; a read never waits for an open transaction
Temporary files of one big GROUP BY16 GB
Programs with the database openone
Number of partitions, rows, tablesno fixed limit

Correlated subqueries

SELECT s.symbol, (SELECT max(close) FROM ochl_day d WHERE d.symbol = s.symbol) AS best FROM securities s;

The subquery uses s.symbol of the outer row. KDB does not run it once per row: it reads ochl_day once, groups it by symbol, and looks every security up in that result. Chapter 23 has the rules.

Try it yourself

  1. What is the result of SELECT 10 / 4, 10 % 4, round(10 / 4)?
  2. Write the number of whole days between 2026-01-01 and 2026-09-10.
  3. Which of these names need double quotes: time, order, left, select, symbol?
Answers
  1. 2.5, 2, 3.
  2. SELECT datediff('d', '2026-01-01', '2026-09-10') gives 252.
  3. order and select always. left only as a short alias without AS. time and symbol never.

Next: 12. Troubleshooting