KDB knowledge

Column types

Every column type, its range, how NULL is kept, and which values it accepts.

Every column has a type. The type says what values the column holds, and how much space one value takes. When a row is written, KDB converts each value to the column type, or refuses the row with a clear message.

TypeAlso writtenSizeValuesUse it for
BOOLBOOLEAN1 byteTRUE, FALSEyes/no flags
BYTE1 byte-127 to 127very small numbers
SHORT2 bytes-32767 to 32767small numbers
INTINTEGER4 bytes-2147483647 to 2147483647broker ids, counts
LONGBIGINT8 bytesabout ±9.2 × 10^18ids, share quantities
FLOAT4 bytesabout 7 digitsrarely needed
DOUBLE8 bytesabout 15 digitsprices, volumes, indicators
DECIMAL(s)8 bytesexact, up to 18 digits, s after the pointmoney
NUMERIC(p, s)DECIMAL(p, s)8 or 16 bytesexact, up to 38 digitsbig money totals, TPoint
DATE4 bytesa daybirthdays, report dates
TIMESTAMP8 bytesa moment in UTC, in millisecondscandle and trade times
SYMBOL4 bytestext that repeats a lotstock symbols, sectors
STRINGVARCHAR, TEXTlength + textany text, up to 16 MBnames, posts, news
JSONJSONBlength + textchecked JSON, up to 16 MBflexible extra data
BYTESBYTEA, BLOBlength + bytesany bytes, up to 16 MBhashes, keys, small files
UUID16 bytesa UUID like a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11ids made by apps, gen_random_uuid()
an ENUM typemade by CREATE TYPE ... AS ENUM4 bytesone label of a fixed listorder status, moods, levels

Example: All basic types in one table

CREATE TABLE type_demo (
  b BOOL, i8 BYTE, i16 SHORT, i32 INT, i64 LONG, f FLOAT, d DOUBLE, m DECIMAL(2),
  day DATE, ts TIMESTAMP, sym SYMBOL, txt STRING
);
INSERT INTO type_demo VALUES
  (TRUE, 127, 32767, 2147483647, 270578640, 1.5, 512.45, 1500.25,
   '2026-09-10', '2026-09-10T11:00:00+05:45', 'CITY', 'City Bank Limited'),
  (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
SELECT * FROM type_demo;

Result:

CREATE TABLE type_demo (

table 'type_demo' created

INSERT INTO type_demo VALUES (TRUE, 127, 32767, 2147483647, 270578640, 1.5, 512.45, 1500.25, '2026-0

2 row(s) inserted

SELECT * FROM type_demo

b (BOOL)i8 (BYTE)i16 (SHORT)i32 (INT)i64 (LONG)f (FLOAT)d (DOUBLE)m (DECIMAL(2))day (DATE)ts (TIMESTAMP)sym (SYMBOL)txt (STRING)
true1273276721474836472705786401.5512.451500.252026-09-102026-09-10T05:15:00.000ZCITYCity Bank Limited
NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL

(2 rows)

BOOL, BOOLEAN

Syntax:

column BOOL
column BOOLEAN

TRUE or FALSE.

Value writtenStored as
TRUE, FALSEitself
1, 0TRUE, FALSE
'true', 'false', 't', 'f', '1', '0' (any case)TRUE, FALSE
anything else (2, 1.0, 'yes')error

Example: Verified users, written in different ways

CREATE TABLE users (id INT PRIMARY KEY, verified BOOL, is_admin BOOLEAN NOT NULL DEFAULT FALSE);
INSERT INTO users (id, verified) VALUES (1, TRUE), (2, 0), (3, 'true'), (4, NULL);
SELECT id, verified, is_admin FROM users;

Result:

CREATE TABLE users (

table 'users' created

INSERT INTO users (id, verified) VALUES (1, TRUE), (2, 0), (3, 'true'), (4, NULL)

4 row(s) inserted

SELECT id, verified, is_admin FROM users

id (INT)verified (BOOL)is_admin (BOOL)
1truefalse
2falsefalse
3truefalse
4NULLfalse

(4 rows)

Example: 'yes' is not a BOOL (shows an error)

Setup:

CREATE TABLE users (id INT PRIMARY KEY, verified BOOL);
INSERT INTO users VALUES (1, 'yes');

Result:

Error: column 'verified' (BOOL): 'yes' is not true or false

BYTE

Syntax:

column BYTE

A whole number from -127 to 127, in 1 byte.

Example: Star rating of a forum post

CREATE TABLE ratings (post_id LONG, stars BYTE NOT NULL);
INSERT INTO ratings VALUES (1, 5), (2, 3);
SELECT post_id, stars FROM ratings;

Result:

CREATE TABLE ratings (

table 'ratings' created

INSERT INTO ratings VALUES (1, 5), (2, 3)

2 row(s) inserted

SELECT post_id, stars FROM ratings

post_id (LONG)stars (BYTE)
15
23

(2 rows)

Example: Too big for BYTE (shows an error)

Setup:

CREATE TABLE t (v BYTE);
INSERT INTO t VALUES (200);

Result:

Error: column 'v' (BYTE): 200 is out of range (-127 to 127)

SHORT

Syntax:

column SHORT

A whole number from -32767 to 32767, in 2 bytes.

Example: Fiscal quarter and year numbers

CREATE TABLE results (symbol SYMBOL, fiscal_year SHORT, quarter SHORT);
INSERT INTO results VALUES ('CITY', 2082, 4);
SELECT symbol, fiscal_year, quarter FROM results;

Result:

CREATE TABLE results (

table 'results' created

INSERT INTO results VALUES ('CITY', 2082, 4)

1 row(s) inserted

SELECT symbol, fiscal_year, quarter FROM results

symbol (SYMBOL)fiscal_year (SHORT)quarter (SHORT)
CITY20824

(1 row)

INT, INTEGER

Syntax:

column INT
column INTEGER

A whole number from -2147483647 to 2147483647, in 4 bytes.

Value writtenStored as
a whole number: 42, -7itself
a DOUBLE with no fraction: 5.0, 1e35, 1000
a DECIMAL with no fraction: 12.0012
text with a whole number: '42'42
TRUE, FALSE1, 0
5.5, '5.0', 'abc'error: not a whole number
a number outside the rangeerror: out of range

Example: Broker ids written as numbers and as text

CREATE TABLE brokers (id INT PRIMARY KEY, name STRING, branch_count INTEGER);
INSERT INTO brokers VALUES (58, 'Northgate Securities', 5), ('45', 'Imperial Securities', 2.0);
SELECT id, name, branch_count FROM brokers ORDER BY id;

Result:

CREATE TABLE brokers (

table 'brokers' created

INSERT INTO brokers VALUES (58, 'Northgate Securities', 5), ('45', 'Imperial Securities', 2.0)

2 row(s) inserted

SELECT id, name, branch_count FROM brokers ORDER BY id

id (INT)name (STRING)branch_count (INT)
45Imperial Securities2
58Northgate Securities5

(2 rows)

Example: A number out of range (shows an error)

Setup:

CREATE TABLE brokers (id INT PRIMARY KEY, name STRING);
INSERT INTO brokers VALUES (3000000000, 'Too Big');

Result:

Error: column 'id' (INT): 3000000000 is out of range (-2147483647 to 2147483647)

Example: A fraction for a whole-number column (shows an error)

Setup:

CREATE TABLE trade_log_small (id INT, quantity INT);
INSERT INTO trade_log_small VALUES (1, 10.5);

Result:

Error: column 'quantity' (INT): 10.5 is not a whole number

LONG, BIGINT

Syntax:

column LONG
column BIGINT

A whole number from -9223372036854775807 to 9223372036854775807, in 8 bytes.

Example: Transaction ids and quantities

CREATE TABLE trades (transaction_id BIGINT PRIMARY KEY, quantity LONG);
INSERT INTO trades VALUES (2026091000012345, 270578640);
SELECT transaction_id, quantity FROM trades;

Result:

CREATE TABLE trades (

table 'trades' created

INSERT INTO trades VALUES (2026091000012345, 270578640)

1 row(s) inserted

SELECT transaction_id, quantity FROM trades

transaction_id (LONG)quantity (LONG)
2026091000012345270578640

(1 row)

FLOAT

Syntax:

column FLOAT

A number with a fraction, in 4 bytes. It keeps about 7 digits, so 512.45 comes back as 512.450012207031.

Example: FLOAT keeps fewer digits than DOUBLE

CREATE TABLE t (f FLOAT, d DOUBLE);
INSERT INTO t VALUES (512.45, 512.45);
SELECT f, d FROM t;

Result:

CREATE TABLE t (

table 't' created

INSERT INTO t VALUES (512.45, 512.45)

1 row(s) inserted

SELECT f, d FROM t

f (FLOAT)d (DOUBLE)
512.450012207031512.45

(1 row)

DOUBLE

Syntax:

column DOUBLE

A number with a fraction, in 8 bytes, with about 15 digits. Fast, and good for prices, volumes, percentages and indicators.

Example: Prices, and why DOUBLE is not for money

CREATE TABLE prices (symbol SYMBOL, ltp DOUBLE, pct_change DOUBLE);
INSERT INTO prices VALUES ('CITY', 527.1, '2.71'), ('COAST', 401.9, 0.7);
SELECT symbol, ltp, pct_change FROM prices;
SELECT 0.1 + 0.2 AS sum, 0.1 + 0.2 = 0.3 AS equal;

Result:

CREATE TABLE prices (

table 'prices' created

INSERT INTO prices VALUES ('CITY', 527.1, '2.71'), ('COAST', 401.9, 0.7)

2 row(s) inserted

SELECT symbol, ltp, pct_change FROM prices

symbol (SYMBOL)ltp (DOUBLE)pct_change (DOUBLE)
CITY527.12.71
COAST401.90.7

(2 rows)

SELECT 0.1 + 0.2 AS sum, 0.1 + 0.2 = 0.3 AS equal

sum (DOUBLE)equal (BOOL)
0.3false

(1 row)

Example: A NaN is stored as NaN, however it is written

CREATE TABLE readings (id INT, v DOUBLE);
INSERT INTO readings VALUES (1, 'NaN'), (2, CAST('NaN' AS DOUBLE)), (3, 12.5);
SELECT id, v, v IS NULL AS is_null, is_nan(v) AS is_nan FROM readings ORDER BY id;

Result:

CREATE TABLE readings (

table 'readings' created

INSERT INTO readings VALUES (1, 'NaN'), (2, CAST('NaN' AS DOUBLE)), (3, 12.5)

3 row(s) inserted

SELECT id, v, v IS NULL AS is_null, is_nan(v) AS is_nan FROM readings ORDER BY id

id (INT)v (DOUBLE)is_null (BOOL)is_nan (BOOL)
1NULLfalsetrue
2NULLfalsetrue
312.5falsefalse

(3 rows)

Example: In a time-series table a NaN is NULL, as in QuestDB

CREATE TABLE bars (time TIMESTAMP, symbol SYMBOL, open DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO bars VALUES ('2026-09-24T05:15:00Z', 'CITY', 'NaN'), ('2026-09-24T05:15:00Z', 'COAST', 0),
  ('2026-09-24T05:15:00Z', 'RIVER', 512.5);
SELECT symbol, open IS NULL AS is_null FROM bars ORDER BY symbol;
SELECT symbol FROM bars WHERE open IS NULL OR open <= 0 ORDER BY symbol;

Result:

CREATE TABLE bars (

table 'bars' created

INSERT INTO bars VALUES ('2026-09-24T05:15:00Z', 'CITY', 'NaN'), ('2026-09-24T05:15:00Z', 'COAST', 0

3 row(s) inserted

SELECT symbol, open IS NULL AS is_null FROM bars ORDER BY symbol

symbol (SYMBOL)is_null (BOOL)
CITYtrue
COASTfalse
RIVERfalse

(3 rows)

SELECT symbol FROM bars WHERE open IS NULL OR open <= 0 ORDER BY symbol

symbol (SYMBOL)
CITY
COAST

(2 rows)

Example: A guard for bad prices: missing, zero or less, or NaN

Setup:

CREATE TABLE ticks (id INT, open DOUBLE);
INSERT INTO ticks VALUES (1, 100), (2, 0), (3, 'NaN'), (4, NULL), (5, -1);
SELECT id FROM ticks WHERE open IS NULL OR open <= 0 OR open = 'NaN' ORDER BY id;
SELECT id, open FROM ticks ORDER BY open;

Result:

SELECT id FROM ticks WHERE open IS NULL OR open <= 0 OR open = 'NaN' ORDER BY id

id (INT)
2
3
4
5

(4 rows)

SELECT id, open FROM ticks ORDER BY open

id (INT)open (DOUBLE)
5-1
20
1100
3NULL
4NULL

(5 rows)

Example: Text that is not a number (shows an error)

Setup:

CREATE TABLE prices (symbol SYMBOL, ltp DOUBLE);
INSERT INTO prices VALUES ('CITY', 'n/a');

Result:

Error: column 'ltp' (DOUBLE): 'n/a' is not a number

DECIMAL(s)

Syntax:

column DECIMAL(scale)
column NUMERIC(precision, scale)

An exact number with scale digits after the point. DECIMAL(2) is right for dollars and cents: 0.10 + 0.20 is exactly 0.30.

ArgumentTypeMeaning
scale0 to 18 (DECIMAL(s)), 0 to 38 (NUMERIC(p, s))digits after the point
precision1 to 38all digits. Up to 18 the value takes 8 bytes; more takes 16
Value writtenStored as (DECIMAL(2))
1500.25, 12, '99.5', '1.500'1500.25, 12.00, 99.50, 1.50
more digits after the point: 10.555error: KDB never rounds money by itself
more than 18 digits: 123456789012345678.12error (numeric value out of range): use NUMERIC(24,2)

Example: A wallet balance: exact money

CREATE TABLE wallet (id INT PRIMARY KEY, balance DECIMAL(2) NOT NULL);
INSERT INTO wallet VALUES (1, 1000.00), (2, '0.10');
UPDATE wallet SET balance = balance - 250.50 WHERE id = 1;
UPDATE wallet SET balance = balance + 0.20 WHERE id = 2;
SELECT id, balance FROM wallet ORDER BY id;
SELECT sum(balance) AS total FROM wallet;

Result:

CREATE TABLE wallet (

table 'wallet' created

INSERT INTO wallet VALUES (1, 1000.00), (2, '0.10')

2 row(s) inserted

UPDATE wallet SET balance = balance - 250.50 WHERE id = 1

1 row(s) updated

UPDATE wallet SET balance = balance + 0.20 WHERE id = 2

1 row(s) updated

SELECT id, balance FROM wallet ORDER BY id

id (INT)balance (DECIMAL(2))
1749.50
20.30

(2 rows)

SELECT sum(balance) AS total FROM wallet

total (DECIMAL(2))
749.80

(1 row)

Example: Interest with too many decimal places is refused (shows an error)

Setup:

CREATE TABLE wallet (id INT PRIMARY KEY, balance DECIMAL(2) NOT NULL);
INSERT INTO wallet VALUES (1, 100.50);
UPDATE wallet SET balance = balance * 1.015 WHERE id = 1;

Result:

Error: column 'balance' (DECIMAL(2)): 102.00750 has more than 2 decimal places

Example: Round it the way your business rule says

Setup:

CREATE TABLE wallet (id INT PRIMARY KEY, balance DECIMAL(2) NOT NULL);
INSERT INTO wallet VALUES (1, 100.50);
UPDATE wallet SET balance = round(balance * 1.015, 2) WHERE id = 1;
SELECT id, balance FROM wallet;

Result:

UPDATE wallet SET balance = round(balance * 1.015, 2) WHERE id = 1

1 row(s) updated

SELECT id, balance FROM wallet

id (INT)balance (DECIMAL(2))
1102.01

(1 row)

Example: Paid-up capital with many digits: exact as written

CREATE TABLE companies (symbol STRING PRIMARY KEY, paid_up_capital DECIMAL(2));
INSERT INTO companies VALUES ('CITY', 27057864000123.45);
SELECT symbol, paid_up_capital FROM companies;

Result:

CREATE TABLE companies (

table 'companies' created

INSERT INTO companies VALUES ('CITY', 27057864000123.45)

1 row(s) inserted

SELECT symbol, paid_up_capital FROM companies

symbol (STRING)paid_up_capital (DECIMAL(2))
CITY27057864000123.45

(1 row)

Example: Exact math: DECIMAL results, as in PostgreSQL

CREATE TABLE orders (id INT PRIMARY KEY, price DECIMAL(2), qty DECIMAL(4));
INSERT INTO orders VALUES (1, 10.25, 3.5), (2, 0.10, 0.2);
SELECT id, price * qty AS amount, price * 1.13 AS with_vat, price / 3 AS third FROM orders ORDER BY id;
SELECT sum(price) AS total, avg(price) AS average FROM orders;

Result:

CREATE TABLE orders (

table 'orders' created

INSERT INTO orders VALUES (1, 10.25, 3.5), (2, 0.10, 0.2)

2 row(s) inserted

SELECT id, price * qty AS amount, price * 1.13 AS with_vat, price / 3 AS third FROM orders ORDER BY

id (INT)amount (DECIMAL(6))with_vat (DECIMAL(4))third (DECIMAL(16))
135.87500011.58253.4166666666666667
20.0200000.11300.03333333333333333333

(2 rows)

SELECT sum(price) AS total, avg(price) AS average FROM orders

total (DECIMAL(2))average (DECIMAL(16))
10.355.1750000000000000

(1 row)

Example: NUMERIC(30,2): money past 18 digits

CREATE TABLE ledger (id INT PRIMARY KEY, amount NUMERIC(30,2));
INSERT INTO ledger VALUES (1, 1234567890123456789012.34), (2, 92233720368547758.07);
SELECT sum(amount) AS total, max(amount) - min(amount) AS spread FROM ledger;
SELECT id, amount * 1000 AS scaled FROM ledger ORDER BY id;

Result:

CREATE TABLE ledger (

table 'ledger' created

INSERT INTO ledger VALUES (1, 1234567890123456789012.34), (2, 92233720368547758.07)

2 row(s) inserted

SELECT sum(amount) AS total, max(amount) - min(amount) AS spread FROM ledger

total (DECIMAL(38,2))spread (DECIMAL(38,2))
1234660123843825336770.411234475656403088241254.27

(1 row)

SELECT id, amount * 1000 AS scaled FROM ledger ORDER BY id

id (INT)scaled (DECIMAL(38,2))
11234567890123456789012340.00
292233720368547758070.00

(2 rows)

Example: Past 38 digits is an error, never a rounded number (shows an error)

SELECT 99999999999999999999999999999999999999 + 1;

Result:

Error: line 1, column 47: numeric value out of range: the result needs more than 38 digits (the most a DECIMAL / NUMERIC holds)

DATE

Syntax:

column DATE

A calendar day without a time, like 2026-09-10, in 4 bytes (days since 1970-01-01).

Value writtenStored as
'2026-09-10'that day
a time as text: '2026-09-10T20:00:00+05:45'the UTC date of that time (here 2026-09-10, because 20:00 Nepal time is 14:15 UTC)
a TIMESTAMPits UTC date
a number, '10/09/2026', '2026-9-10'error

Example: Company listing dates and report dates

CREATE TABLE companies (symbol STRING PRIMARY KEY, listed_on DATE, last_report DATE);
INSERT INTO companies VALUES ('CITY', '1985-07-12', '2026-07-16'), ('VOLT', '2019-01-21', NULL);
SELECT symbol, listed_on, last_report, last_report + 90 AS next_report_due FROM companies;

Result:

CREATE TABLE companies (

table 'companies' created

INSERT INTO companies VALUES ('CITY', '1985-07-12', '2026-07-16'), ('VOLT', '2019-01-21', NULL)

2 row(s) inserted

SELECT symbol, listed_on, last_report, last_report + 90 AS next_report_due FROM companies

symbol (STRING)listed_on (DATE)last_report (DATE)next_report_due (DATE)
CITY1985-07-122026-07-162026-10-14
VOLT2019-01-21NULLNULL

(2 rows)

Example: A date in another format (shows an error)

Setup:

CREATE TABLE companies (symbol STRING PRIMARY KEY, listed_on DATE);
INSERT INTO companies VALUES ('CITY', '12/07/1985');

Result:

Error: column 'listed_on' (DATE): '12/07/1985' is not a date like '2026-09-11'

TIMESTAMP

Syntax:

column TIMESTAMP [ROUND interval]

A moment in time, stored as milliseconds since 1970-01-01 UTC, in 8 bytes. KDB always shows it in UTC with a Z: 2026-09-10T05:15:00.000Z.

Value writtenRead as
'2026-09-10T11:00:00+05:45' (or +0545)11:00 Nepal time = 05:15 UTC
'2026-09-10T07:00:00-04:00'07:00 New York summer time = 11:00 UTC
'2026-09-10 11:00:00' (no zone)11:00 UTC (not local time!)
'2026-09-10T11:00:00Z'11:00 UTC
'2026-09-10'00:00 UTC
'2026-09-10T05:15:00.250Z'with milliseconds; more than 3 digits are cut
1789017300000milliseconds since 1970-01-01 UTC
a DATE00:00 UTC of that day
'10/09/2026', '2026-9-10', '2026-02-30', 1.5, TRUEerror

Example: The same market open (11:00 in Nepal) written in different ways

CREATE TABLE time_test (time TIMESTAMP, note STRING);
INSERT INTO time_test VALUES
  ('2026-09-10T11:00:00+05:45', 'Nepal time with +05:45'),
  ('2026-09-10T11:00:00+0545',  'Nepal time without the colon'),
  ('2026-09-10 05:15:00',       'no zone = UTC'),
  ('2026-09-10T05:15:00Z',      'Z = UTC'),
  (1789017300000,               'milliseconds since 1970'),
  ('2026-09-10T11:00:00',       'Nepal time WITHOUT +05:45: wrong');
SELECT time, note FROM time_test;

Result:

CREATE TABLE time_test (

table 'time_test' created

INSERT INTO time_test VALUES ('2026-09-10T11:00:00+05:45', 'Nepal time with +05:45'), ('2026-09-10T1

6 row(s) inserted

SELECT time, note FROM time_test

time (TIMESTAMP)note (STRING)
2026-09-10T05:15:00.000ZNepal time with +05:45
2026-09-10T05:15:00.000ZNepal time without the colon
2026-09-10T05:15:00.000Zno zone = UTC
2026-09-10T05:15:00.000ZZ = UTC
2026-09-10T05:15:00.000Zmilliseconds since 1970
2026-09-10T11:00:00.000ZNepal time WITHOUT +05:45: wrong

(6 rows)

Example: A time in a local format (shows an error)

Setup:

CREATE TABLE trades (time TIMESTAMP, symbol SYMBOL);
INSERT INTO trades VALUES ('10/09/2026 11:00', 'CITY');

Result:

Error: column 'time' (TIMESTAMP): '10/09/2026 11:00' is not a time like '2026-09-11 11:00:00' (UTC) or '2026-09-11T11:00:00+05:45'

SYMBOL

Syntax:

column SYMBOL

Text that repeats a lot, like stock symbols, sectors or order types. Each different text is kept once in a list; every row keeps only a 4-byte number that points into the list. So a SYMBOL column of millions of rows is small and fast to compare.

Example: Symbols and sectors

CREATE TABLE securities (symbol SYMBOL PRIMARY KEY, name STRING NOT NULL, sector SYMBOL);
INSERT INTO securities VALUES ('CITY', 'City Bank Limited', 'Banking'), ('COAST', 'Coastal Bank Limited', 'Banking');
SELECT sector, count(*) AS companies FROM securities GROUP BY sector;

Result:

CREATE TABLE securities (

table 'securities' created

INSERT INTO securities VALUES ('CITY', 'City Bank Limited', 'Banking'), ('COAST', 'Coastal Bank Limi

2 row(s) inserted

SELECT sector, count(*) AS companies FROM securities GROUP BY sector

sector (SYMBOL)companies (LONG)
Banking2

(1 row)

ENUM types (CREATE TYPE)

Syntax:

CREATE TYPE name AS ENUM ('label', 'label', ...)
ALTER TYPE name ADD VALUE [IF NOT EXISTS] 'label' [BEFORE | AFTER 'label']
DROP TYPE [IF EXISTS] name [, name ...]
column name

A fixed list of labels, as in PostgreSQL. You make the type once, then use its name as a column type. A column of the type takes only its labels. It is kept like a SYMBOL: 4 bytes a row.

RuleWhat happens
Valuemust be one of the labels, exactly (case counts); anything else is refused with invalid input value for enum ... (Postgres code 22P02)
OrderORDER BY, <, <=, >, >= and BETWEEN follow the order the labels were listed in, not the alphabet. This works through a view, a subquery and a WITH query too. The other side is a label: 'paid', 'paid'::order_status or CAST('paid' AS order_status). Against another column or an expression, the text is compared
Cast'happy'::mood and CAST('happy' AS mood) check the label too
New labelALTER TYPE ... ADD VALUE puts it at the end, or BEFORE / AFTER a label
Change typeALTER TABLE ... ALTER COLUMN c TYPE other_type checks every row: it is refused if a row holds a value that is not a label of the new type
DropDROP TYPE is refused while a column uses the type (code 2BP01); drop or change the column first

Example: Order status in the order the order moves

CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'delivered');
CREATE TABLE orders (id INT PRIMARY KEY, status order_status NOT NULL DEFAULT 'new');
INSERT INTO orders VALUES (1, 'shipped'), (2, 'new'), (3, 'delivered'), (4, 'paid');
SELECT id, status FROM orders ORDER BY status;
SELECT id FROM orders WHERE status >= 'shipped' ORDER BY id;

Result:

CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'delivered')

type 'order_status' created

CREATE TABLE orders (

table 'orders' created

INSERT INTO orders VALUES (1, 'shipped'), (2, 'new'), (3, 'delivered'), (4, 'paid')

4 row(s) inserted

SELECT id, status FROM orders ORDER BY status

id (INT)status (SYMBOL)
2new
4paid
1shipped
3delivered

(4 rows)

SELECT id FROM orders WHERE status >= 'shipped' ORDER BY id

id (INT)
1
3

(2 rows)

Example: Label order through a view, with a cast

CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'delivered');
CREATE TABLE orders (id INT PRIMARY KEY, status order_status);
INSERT INTO orders VALUES (1, 'shipped'), (2, 'new'), (3, 'delivered'), (4, 'paid');
CREATE VIEW open_orders AS SELECT * FROM orders;
SELECT id FROM open_orders WHERE status < 'shipped'::order_status ORDER BY id;

Result:

CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'delivered')

type 'order_status' created

CREATE TABLE orders (

table 'orders' created

INSERT INTO orders VALUES (1, 'shipped'), (2, 'new'), (3, 'delivered'), (4, 'paid')

4 row(s) inserted

CREATE VIEW open_orders AS SELECT * FROM orders

view 'open_orders' created

SELECT id FROM open_orders WHERE status < CAST('shipped' AS order_status) ORDER BY id

id (INT)
2
4

(2 rows)

Example: Only the labels are taken (shows an error)

Setup:

CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'delivered');
CREATE TABLE orders (id INT PRIMARY KEY, status order_status);
INSERT INTO orders VALUES (1, 'lost');

Result:

Error: invalid input value for enum order_status: "lost"

Example: Add a label between two others

CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'delivered');
ALTER TYPE order_status ADD VALUE 'packed' AFTER 'paid';
SELECT enumlabel FROM pg_enum ORDER BY enumsortorder;

Result:

CREATE TYPE order_status AS ENUM ('new', 'paid', 'shipped', 'delivered')

type 'order_status' created

ALTER TYPE order_status ADD VALUE 'packed' AFTER 'paid'

type 'order_status' altered: label 'packed' added

SELECT enumlabel FROM pg_enum ORDER BY enumsortorder

enumlabel (STRING)
new
paid
packed
shipped
delivered

(5 rows)

Example: A type in use cannot be dropped (shows an error)

Setup:

CREATE TYPE order_status AS ENUM ('new', 'paid');
CREATE TABLE orders (id INT PRIMARY KEY, status order_status);
DROP TYPE order_status;

Result:

Error: cannot drop type order_status because other objects depend on it: column 'status' of table 'orders' uses it; drop that column or change its type first

STRING, VARCHAR, TEXT

Syntax:

column STRING
column VARCHAR
column TEXT

Any text in UTF-8 (any language: English, Nepali, Chinese, emoji), up to 16 MB per value. VARCHAR and TEXT are the same type.

Example: A news article in two languages

CREATE TABLE news (id LONG PRIMARY KEY AUTO, lang STRING NOT NULL DEFAULT 'en', title VARCHAR NOT NULL, body TEXT NOT NULL);
INSERT INTO news (lang, title, body) VALUES
  ('en', 'City Bank profit rises 18%', 'City Bank reported a net profit of $3.27 billion.'),
  ('ne', 'सिटी बैंकको नाफा बढ्यो', 'सिटी बैंकले ३.२७ अर्ब नाफा कमायो।');
SELECT id, lang, title FROM news ORDER BY id;

Result:

CREATE TABLE news (

table 'news' created

INSERT INTO news (lang, title, body) VALUES ('en', 'City Bank profit rises 18%', 'City Bank reported

2 row(s) inserted

SELECT id, lang, title FROM news ORDER BY id

id (LONG)lang (STRING)title (STRING)
1enCity Bank profit rises 18%
2neसिटी बैंकको नाफा बढ्यो

(2 rows)

Example: VARCHAR(255): the number is read and ignored

Setup:

CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255));
SHOW COLUMNS FROM users;

Result:

name (STRING)type (STRING)primary_key (BOOL)not_null (BOOL)computed (BOOL)references (STRING)
idINTtruefalsefalseNULL
emailSTRINGfalsefalsefalseNULL

(2 rows)

JSON, JSONB

Syntax:

column JSON
column JSONB

A JSON document (objects, arrays, text, numbers, true, false, null), up to 16 MB. JSONB is the same type. KDB checks that the text is valid JSON and removes the spaces between the parts.

Value writtenStored as
JSON text: '{ "a" : 1 }'{"a":1} (keys stay in their order; numbers keep their digits, 2.50 stays 2.50)
a number or TRUE/FALSE5, true
bad JSON: '{"a": 1,}', 'hello'error with the character position

Example: Company profiles that differ per company

CREATE TABLE companies (symbol STRING PRIMARY KEY, profile JSON DEFAULT '{}');
INSERT INTO companies VALUES
  ('CITY', '{ "head_office": "Kathmandu", "branches": 270 }'),
  ('VOLT', '{"capacity_mw": 456, "rivers": ["Volt River"]}');
INSERT INTO companies (symbol) VALUES ('COAST');
SELECT symbol, CAST(profile AS STRING) AS profile, profile ->> 'head_office' AS office FROM companies ORDER BY symbol;

Result:

CREATE TABLE companies (

table 'companies' created

INSERT INTO companies VALUES ('CITY', '{ "head_office": "Kathmandu", "branches": 270 }'), ('VOLT', '

2 row(s) inserted

INSERT INTO companies (symbol) VALUES ('COAST')

1 row(s) inserted

SELECT symbol, CAST(profile AS STRING) AS profile, profile ->> 'head_office' AS office FROM companie

symbol (STRING)profile (STRING)office (STRING)
CITY{"head_office":"Kathmandu","branches":270}Kathmandu
COAST{}NULL
VOLT{"capacity_mw":456,"rivers":["Volt River"]}NULL

(3 rows)

Example: JSON of different kinds, and JSON null

CREATE TABLE settings (id INT PRIMARY KEY, value JSONB);
INSERT INTO settings VALUES (1, '[1, 2.50]'), (2, 5), (3, TRUE), (4, 'null'), (5, NULL);
SELECT id, CAST(value AS STRING) AS stored_text, value IS NULL AS is_sql_null FROM settings ORDER BY id;

Result:

CREATE TABLE settings (

table 'settings' created

INSERT INTO settings VALUES (1, '[1, 2.50]'), (2, 5), (3, TRUE), (4, 'null'), (5, NULL)

5 row(s) inserted

SELECT id, CAST(value AS STRING) AS stored_text, value IS NULL AS is_sql_null FROM settings ORDER BY

id (INT)stored_text (STRING)is_sql_null (BOOL)
1[1,2.50]false
25false
3truefalse
4nullfalse
5NULLtrue

(5 rows)

Example: Bad JSON is refused (shows an error)

Setup:

CREATE TABLE fundamentals (id INT PRIMARY KEY, extra JSON);
INSERT INTO fundamentals VALUES (1, '{"npl_pct": 4.3,}');

Result:

Error: column 'extra' (JSON): not valid JSON: a JSON object cannot end with ',' at character 17 (near '}')

BYTES, BYTEA, BLOB

Syntax:

column BYTES
column BYTEA
column BLOB
X'0A0B'    '\x0a0b'

Any bytes, also zero bytes, up to 16 MB. BYTEA and BLOB are the same type. Good for password salts, SHA-256 hashes, keys, signatures and small images.

Value writtenStored as
X'0A0B' (hex, right after the X)the bytes 0A 0B
'\x0a0b' (text that starts with \x)the bytes 0A 0B
other text: 'hi'its UTF-8 bytes (68 69)
a numbererror

Example: The SHA-256 hash of a report PDF

CREATE TABLE reports (id LONG PRIMARY KEY AUTO, pdf_url STRING, pdf_sha256 BYTES);
INSERT INTO reports (pdf_url, pdf_sha256) VALUES
  ('https://files.example.com/city/2082-83-q4.pdf', X'9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08'),
  ('https://files.example.com/volt/2082-83-q4.pdf', NULL);
SELECT id, pdf_sha256, length(pdf_sha256) AS bytes FROM reports ORDER BY id;

Result:

CREATE TABLE reports (

table 'reports' created

INSERT INTO reports (pdf_url, pdf_sha256) VALUES ('https://files.example.com/city/2082-83-q4.pdf', u

2 row(s) inserted

SELECT id, pdf_sha256, length(pdf_sha256) AS bytes FROM reports ORDER BY id

id (LONG)pdf_sha256 (BYTES)bytes (LONG)
1\x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a0832
2NULLNULL

(2 rows)

Example: Three ways to write bytes

CREATE TABLE keys (id INT PRIMARY KEY, k BYTEA, b BLOB);
INSERT INTO keys VALUES (1, X'00FF10', '\x00ff10'), (2, 'hi', X'');
SELECT id, k, b FROM keys ORDER BY id;

Result:

CREATE TABLE keys (

table 'keys' created

INSERT INTO keys VALUES (1, unhex('00FF10'), '\x00ff10'), (2, 'hi', unhex(''))

2 row(s) inserted

SELECT id, k, b FROM keys ORDER BY id

id (INT)k (BYTES)b (BYTES)
1\x00ff10\x00ff10
2\x6869\x

(2 rows)

Example: Hex with a bad digit (shows an error)

Setup:

CREATE TABLE keys (id INT PRIMARY KEY, k BYTES);
INSERT INTO keys VALUES (1, '\xZZ');

Result:

Error: column 'k' (BYTES): '\xZZ' is not hex bytes; write them like '\x0a0b' or X'0A0B'

UUID

Syntax:

column UUID
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid

A UUID, kept in 16 bytes (as text it would take 36 characters and more). It reads back as text: 36 lower-case characters with dashes, as in PostgreSQL.

Value writtenStored as
'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'itself
upper case, {...} braces, or the 32 digits without dashesthe same UUID: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
gen_random_uuid()a new random UUID (version 4)
text that is no UUID: 'abc'error invalid input syntax for type uuid (SQLSTATE 22P02)

Example: Turn a text column of UUIDs into a UUID column

CREATE TABLE devices (id TEXT, name STRING);
INSERT INTO devices VALUES ('A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11', 'Gate'), ('{c0eebc999c0b4ef8bb6d6bb9bd380a13}', 'Pump');
ALTER TABLE devices ALTER COLUMN id TYPE UUID;
SELECT id, name FROM devices ORDER BY id;

Result:

CREATE TABLE devices (

table 'devices' created

INSERT INTO devices VALUES ('A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11', 'Gate'), ('{c0eebc999c0b4ef8bb6d

2 row(s) inserted

ALTER TABLE devices ALTER COLUMN id TYPE UUID

table 'devices' altered (2 row change(s) written)

SELECT id, name FROM devices ORDER BY id

id (UUID)name (STRING)
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11Gate
c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a13Pump

(2 rows)

Example: A value that is no UUID stops the change (shows an error)

Setup:

CREATE TABLE devices (id TEXT, name STRING);
INSERT INTO devices VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Gate'), ('pump-1', 'Pump');
ALTER TABLE devices ALTER COLUMN id TYPE UUID;

Result:

Error: cannot change column 'id' of table 'devices': a row already there does not fit (column 'id' (UUID): invalid input syntax for type uuid: 'pump-1')

Example: Users keyed by a UUID

CREATE TABLE app_users (id UUID PRIMARY KEY, name STRING);
INSERT INTO app_users VALUES
  ('A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11', 'Asha'),
  ('{b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12}', 'Bikash');
SELECT id, name FROM app_users ORDER BY id;
SELECT name FROM app_users WHERE id = 'a0eebc999c0b4ef8bb6d6bb9bd380a11';

Result:

CREATE TABLE app_users (

table 'app_users' created

INSERT INTO app_users VALUES ('A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11', 'Asha'), ('{b0eebc99-9c0b-4ef8

2 row(s) inserted

SELECT id, name FROM app_users ORDER BY id

id (UUID)name (STRING)
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11Asha
b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12Bikash

(2 rows)

SELECT name FROM app_users WHERE id = 'a0eebc999c0b4ef8bb6d6bb9bd380a11'

name (STRING)
Asha

(1 row)

Example: Text that is no UUID is refused (shows an error)

Setup:

CREATE TABLE app_users (id UUID PRIMARY KEY, name STRING);
INSERT INTO app_users VALUES ('not-a-uuid', 'Chandra');

Result:

Error: column 'id' (UUID): invalid input syntax for type uuid: 'not-a-uuid'