Constraints
Rules that keep table data correct: PRIMARY KEY, AUTO, NOT NULL, UNIQUE, DEFAULT, REFERENCES and CHECK.
- Category: Constraints
- Keywords: constraint, rule, column rule, primary key, key, auto increment, autoincrement, serial, identity, sequence, AUTO, not null, required, unique, duplicate, default value, DEFAULT, foreign key, references, REFERENCES, parent row, ON DELETE CASCADE, check, CHECK, CONSTRAINT, validation, data integrity, cannot be NULL, must be unique, has no matching row, still refers to it, CHECK constraint failed, NaN, bad price, price check
- See also: CREATE TABLE, ALTER, RENAME and DROP TABLE, INSERT, UPDATE and DELETE, Indexes, Transactions
A constraint is a rule for the rows of a table. KDB checks the rules every time a row is written (INSERT, INSERT OR REPLACE, UPDATE, the C++ InsertRows, the line protocol). A row that breaks a rule is not written, and the whole statement fails. So your app can never store a user without an email, a negative balance, or a trade for a broker that does not exist.
| Rule | Meaning | Error when broken |
|---|---|---|
PRIMARY KEY | one row per value; never NULL | a row with id='1' already exists |
AUTO | KDB gives the next id | |
NOT NULL | the column must have a value | cannot be NULL |
UNIQUE | no two rows with the same value | must be unique: ... is already used |
DEFAULT | the value when an INSERT gives none | |
REFERENCES | the value must exist in another table | has no matching row in / still refers to it |
CHECK | a condition every row must meet | CHECK constraint '...' failed |
A real example: the accounting tables of an app.
Example: Users, accounts and a ledger with every kind of rule
CREATE TABLE users (
id INT PRIMARY KEY AUTO,
email STRING NOT NULL UNIQUE,
password_hash STRING NOT NULL,
created TIMESTAMP DEFAULT now()
);
CREATE TABLE accounts (
id INT PRIMARY KEY AUTO,
user_id INT NOT NULL REFERENCES users(id),
balance DECIMAL(2) NOT NULL DEFAULT 0 CHECK (balance >= 0)
);
CREATE TABLE ledger (
id LONG PRIMARY KEY AUTO,
from_account INT NOT NULL REFERENCES accounts(id),
to_account INT NOT NULL REFERENCES accounts(id),
amount DECIMAL(2) NOT NULL,
created TIMESTAMP DEFAULT now(),
CONSTRAINT positive_amount CHECK (amount > 0),
CONSTRAINT two_accounts CHECK (from_account != to_account)
);
SHOW CREATE TABLE ledger;
Result:
CREATE TABLE users (
table 'users' created
CREATE TABLE accounts (
table 'accounts' created
CREATE TABLE ledger (
table 'ledger' created
SHOW CREATE TABLE ledger
| create_sql (STRING) |
|---|
| CREATE TABLE ledger ( id LONG PRIMARY KEY AUTO, from_account INT NOT NULL REFERENCES accounts(id), to_account INT NOT NULL REFERENCES accounts(id), amount DECIMAL(2) NOT NULL, created TIM... |
(1 row)
PRIMARY KEY
Syntax:
column TYPE PRIMARY KEY [AUTO]
The column that identifies a row. There is one row per value. Rows are kept in key order, so finding a row by its key is fast.
- At most one
PRIMARY KEYper table, on one column. For a key over many columns, useCREATE UNIQUE INDEX(indexes). - A key can never be
NULL(you do not need to writeNOT NULL). UPDATEmay change a key; it is refused when the new value is already used.- Returns: a duplicate key gives
a row with id='...' already exists in 't'; nothing of that statement is saved. UseINSERT OR REPLACEorINSERT OR IGNOREto decide what happens (INSERT). - Notes: only for relational tables. A time-series table uses DEDUP KEY instead. You cannot add a primary key with
ALTER TABLElater.
Example: One row per broker
CREATE TABLE brokers (id INT PRIMARY KEY, name STRING NOT NULL);
INSERT INTO brokers VALUES (58, 'Northgate Securities'), (1, 'Keystone Securities');
SELECT id, name FROM brokers;
Result:
CREATE TABLE brokers (
table 'brokers' created
INSERT INTO brokers VALUES (58, 'Northgate Securities'), (1, 'Keystone Securities')
2 row(s) inserted
SELECT id, name FROM brokers
| id (INT) | name (STRING) |
|---|---|
| 1 | Keystone Securities |
| 58 | Northgate Securities |
(2 rows)
Example: The same broker number twice (shows an error)
Setup:
CREATE TABLE brokers (id INT PRIMARY KEY, name STRING NOT NULL);
INSERT INTO brokers VALUES (58, 'Northgate Securities');
INSERT INTO brokers VALUES (58, 'Another Broker');
Result:
Error: a row with id='58' already exists in 'brokers'. Use INSERT OR REPLACE to overwrite it, or INSERT OR IGNORE to skip it
Example: A key cannot be NULL (shows an error)
Setup:
CREATE TABLE securities (symbol SYMBOL PRIMARY KEY, name STRING);
INSERT INTO securities (name) VALUES ('No symbol');
Result:
Error: column 'symbol' (SYMBOL): cannot be NULL
PRIMARY KEY AUTO
Syntax:
column BYTE | SHORT | INT | LONG PRIMARY KEY AUTO
When an INSERT gives no value (or NULL) for the key, KDB uses the next number of the table's counter: 1, 2, 3, ... This is like SERIAL or IDENTITY in PostgreSQL.
- You may still give an id yourself. A bigger id moves the counter up: after id 10, the next automatic id is 11.
- The counter only goes up. The id of a deleted row, of a failed insert, or of a rolled-back transaction is never used again, so there can be gaps (1, 2, 4, ...).
- Returns: use
RETURNING idto get the new id back (RETURNING). - Notes: only on an integer
PRIMARY KEY. Do not use AUTO ids to count rows. When the counter passes the type's range (127 forBYTE), the insert fails; useLONGfor big tables.
Example: New forum posts get ids 1, 2, 3
CREATE TABLE posts (id LONG PRIMARY KEY AUTO, author STRING NOT NULL, body STRING NOT NULL);
INSERT INTO posts (author, body) VALUES ('ram', 'Is CITY a good buy?'), ('sita', 'I agree, the profit grew 18%.');
INSERT INTO posts (author, body) VALUES ('hari', 'Price is high now.');
SELECT id, author, body FROM posts;
Result:
CREATE TABLE posts (
table 'posts' created
INSERT INTO posts (author, body) VALUES ('ram', 'Is CITY a good buy?'), ('sita', 'I agree, the profi
2 row(s) inserted
INSERT INTO posts (author, body) VALUES ('hari', 'Price is high now.')
1 row(s) inserted
SELECT id, author, body FROM posts
| id (LONG) | author (STRING) | body (STRING) |
|---|---|---|
| 1 | ram | Is CITY a good buy? |
| 2 | sita | I agree, the profit grew 18%. |
| 3 | hari | Price is high now. |
(3 rows)
Example: Ids of deleted rows are not used again
Setup:
CREATE TABLE posts (id LONG PRIMARY KEY AUTO, body STRING NOT NULL);
INSERT INTO posts (body) VALUES ('first'), ('second'), ('third');
DELETE FROM posts WHERE id = 3;
INSERT INTO posts (id, body) VALUES (10, 'imported with its old id');
INSERT INTO posts (body) VALUES ('new post');
SELECT id, body FROM posts;
Result:
DELETE FROM posts WHERE id = 3
1 row(s) deleted
INSERT INTO posts (id, body) VALUES (10, 'imported with its old id')
1 row(s) inserted
INSERT INTO posts (body) VALUES ('new post')
1 row(s) inserted
SELECT id, body FROM posts
| id (LONG) | body (STRING) |
|---|---|
| 1 | first |
| 2 | second |
| 10 | imported with its old id |
| 11 | new post |
(4 rows)
Example: AUTO needs a whole-number column (shows an error)
CREATE TABLE users (id STRING PRIMARY KEY AUTO, email STRING);
Result:
Error: line 1, column 43: PRIMARY KEY AUTO needs an integer column; expected BYTE, SHORT, INT or LONG but column 'id' is STRING
NOT NULL
Syntax:
column TYPE NOT NULL
The column must always have a value. An INSERT that gives no value (and there is no DEFAULT), or gives NULL, fails. An UPDATE ... SET column = NULL fails too.
- Returns:
column 'name' (STRING): cannot be NULL. - Notes: a
DEFAULTfills the column only when theINSERTdoes not name it. An explicitNULLis still refused. To add aNOT NULLcolumn to a table with rows, give it aDEFAULT(ADD COLUMN). The time column of a time-series table always needs a value:the time column 'time' needs a value.
Example: Every user needs an email
CREATE TABLE users (id LONG PRIMARY KEY AUTO, email STRING NOT NULL, phone STRING);
INSERT INTO users (email) VALUES ('ram@example.com');
SELECT id, email, phone FROM users;
Result:
CREATE TABLE users (
table 'users' created
INSERT INTO users (email) VALUES ('ram@example.com')
1 row(s) inserted
SELECT id, email, phone FROM users
| id (LONG) | email (STRING) | phone (STRING) |
|---|---|---|
| 1 | ram@example.com | NULL |
(1 row)
Example: A user without an email (shows an error)
Setup:
CREATE TABLE users (id LONG PRIMARY KEY AUTO, email STRING NOT NULL, phone STRING);
INSERT INTO users (phone) VALUES ('9800000000');
Result:
Error: column 'email' (STRING): cannot be NULL
Example: NOT NULL with a DEFAULT: leaving it out is fine, NULL is not (shows an error)
Setup:
CREATE TABLE accounts (id INT PRIMARY KEY AUTO, status STRING NOT NULL DEFAULT 'active');
INSERT INTO accounts (id) VALUES (1);
SELECT id, status FROM accounts;
INSERT INTO accounts (id, status) VALUES (2, NULL);
Result:
INSERT INTO accounts (id) VALUES (1)
1 row(s) inserted
SELECT id, status FROM accounts
| id (INT) | status (STRING) |
|---|---|
| 1 | active |
(1 row)
Error: column 'status' (STRING): cannot be NULL
UNIQUE
Syntax:
column TYPE UNIQUE
No two rows may have the same value in this column. NULL does not count: many rows may have NULL.
- A table may have many
UNIQUEcolumns (email, phone, username). - Checked on
INSERT,INSERT OR REPLACEandUPDATE, and also between the rows of one statement. - Returns:
column 'email' must be unique: 'ram@example.com' is already used. - Notes:
INSERT OR REPLACEreplaces a row with the same key, not a row with the same unique value: a clash on aUNIQUEcolumn is still an error. For a rule over many columns (one row per symbol, year and quarter), useCREATE UNIQUE INDEX(indexes). Text compares exactly:'Ram@Example.com'and'ram@example.com'are different; store emails in lower case.
Example: One account per email; many users without a phone
CREATE TABLE users (id LONG PRIMARY KEY AUTO, email STRING NOT NULL UNIQUE, phone STRING UNIQUE);
INSERT INTO users (email, phone) VALUES ('ram@example.com', NULL), ('sita@example.com', NULL), ('hari@example.com', '9800000000');
SELECT id, email, phone FROM users;
Result:
CREATE TABLE users (
table 'users' created
INSERT INTO users (email, phone) VALUES ('ram@example.com', NULL), ('sita@example.com', NULL), ('har
3 row(s) inserted
SELECT id, email, phone FROM users
| id (LONG) | email (STRING) | phone (STRING) |
|---|---|---|
| 1 | ram@example.com | NULL |
| 2 | sita@example.com | NULL |
| 3 | hari@example.com | 9800000000 |
(3 rows)
Example: Register the same email twice (shows an error)
Setup:
CREATE TABLE users (id LONG PRIMARY KEY AUTO, email STRING NOT NULL UNIQUE);
INSERT INTO users (email) VALUES ('ram@example.com');
INSERT INTO users (email) VALUES ('ram@example.com');
Result:
Error: column 'email' must be unique: 'ram@example.com' is already used
Example: Change an email to one that is taken (shows an error)
Setup:
CREATE TABLE users (id LONG PRIMARY KEY AUTO, email STRING NOT NULL UNIQUE);
INSERT INTO users (email) VALUES ('ram@example.com'), ('sita@example.com');
UPDATE users SET email = 'ram@example.com' WHERE id = 2;
Result:
Error: column 'email' must be unique: 'ram@example.com' is already used
DEFAULT
Syntax:
column TYPE DEFAULT expression
The value a column gets when an INSERT does not name it. Also used by INSERT OR REPLACE for columns you do not give, and for old rows by ALTER TABLE ... ADD COLUMN.
| Default | Example |
|---|---|
| a constant | DEFAULT 0, DEFAULT 'active', DEFAULT FALSE, DEFAULT '{}' for JSON |
| an expression without columns | DEFAULT -1, DEFAULT 60 * 24 |
| a function | DEFAULT now(), DEFAULT gen_random_uuid(), DEFAULT random_token() |
now()and random functions are worked out for each inserted row.- Notes: a default cannot use columns (write a computed column
AS (...)for that) or subqueries. A constant must fit the column type:INT DEFAULT 'x'is refused when the table is created.NOT NULL DEFAULT NULLis refused. An explicitNULLin anINSERTdoes not use the default. There is noDEFAULTkeyword insideVALUES (...): leave the column out of the column list instead.
Example: New accounts start active, with 0.00 and a creation time
CREATE TABLE accounts (
id INT PRIMARY KEY AUTO,
owner STRING NOT NULL,
balance DECIMAL(2) NOT NULL DEFAULT 0,
status STRING NOT NULL DEFAULT 'active',
opened TIMESTAMP DEFAULT now()
);
INSERT INTO accounts (owner) VALUES ('Ram Sharma');
INSERT INTO accounts (owner, balance, status) VALUES ('Sita Rai', 99.50, 'frozen');
SELECT id, owner, balance, status, opened IS NOT NULL AS has_open_time FROM accounts;
Result:
CREATE TABLE accounts (
table 'accounts' created
INSERT INTO accounts (owner) VALUES ('Ram Sharma')
1 row(s) inserted
INSERT INTO accounts (owner, balance, status) VALUES ('Sita Rai', 99.50, 'frozen')
1 row(s) inserted
SELECT id, owner, balance, status, opened IS NOT NULL AS has_open_time FROM accounts
| id (INT) | owner (STRING) | balance (DECIMAL(2)) | status (STRING) | has_open_time (BOOL) |
|---|---|---|---|---|
| 1 | Ram Sharma | 0.00 | active | true |
| 2 | Sita Rai | 99.50 | frozen | true |
(2 rows)
Example: A default of the wrong type (shows an error)
CREATE TABLE posts (id LONG PRIMARY KEY AUTO, likes INT DEFAULT 'none');
Result:
Error: column 'likes' of table 'posts' is INT but its DEFAULT 'none' is text; expected a whole number
Example: A default cannot use another column (shows an error)
CREATE TABLE posts (id LONG PRIMARY KEY AUTO, created TIMESTAMP, edited TIMESTAMP DEFAULT created);
Result:
Error: column 'edited' of table 'posts' has DEFAULT using column 'created'; expected a constant such as 0 or 'n/a', or AS (...) for a computed column
REFERENCES
Syntax:
column TYPE REFERENCES table(column)
The value must exist in the other table (the parent). This is a foreign key. Use it so a trade cannot point to a broker that does not exist, and a ledger row cannot point to a missing account.
| Argument | Meaning |
|---|---|
table | the parent table; it must exist already (or be the same table) |
column | the parent column; it must be the PRIMARY KEY or UNIQUE, with the same kind of type (any integer type fits any integer type; a SYMBOL may point to a STRING) |
What KDB checks:
| Action | Result |
|---|---|
INSERT or UPDATE of a child row with a value the parent does not have | error: column 'buyer' = '99' has no matching row in brokers(id) |
NULL in the child column | allowed, not checked (add NOT NULL to forbid it) |
DELETE of a parent row that children still use, or UPDATE of its key | error: cannot remove brokers(id) = '58': ... still refers to it |
INSERT OR REPLACE of a parent row with the same key | allowed |
DROP TABLE of the parent | error while a child column references it |
- Notes:
ON DELETE/ON UPDATEtakeNO ACTION(the default: refuse while children point to the row),RESTRICT,CASCADE(delete the children too; ON UPDATE CASCADE follows the new key),SET NULLandSET DEFAULT. All children are changed in the same statement, or none. Children thatSET NULL,SET DEFAULTorON UPDATE CASCADEchange are an update of the child table: itsBEFORE UPDATErow triggers andUPDATEtriggers run for them (as in PostgreSQL), and a trigger that refuses stops the whole statement. What such a trigger changes is checked like a plain UPDATE (references, CHECK, the time column); it may not change a UNIQUE or key column there - do that with a separateUPDATE. ChildrenON DELETE CASCADEremoves fire itsDELETEtriggers. Load the parent table before the child table. A new row cannot point to a row inserted in the same statement (a reply and its parent post in oneINSERT): insert the parent first. REFERENCES checks every new row, so very big imports are faster without it.
Example: Trades must use real brokers
Setup:
CREATE TABLE brokers (id INT PRIMARY KEY, name STRING NOT NULL);
CREATE TABLE trades (id LONG PRIMARY KEY AUTO, symbol SYMBOL, buyer INT NOT NULL REFERENCES brokers(id), seller INT REFERENCES brokers(id));
INSERT INTO brokers VALUES (58, 'Northgate Securities'), (45, 'Imperial Securities');
INSERT INTO trades (symbol, buyer, seller) VALUES ('CITY', 58, 45), ('COAST', 45, NULL);
SELECT id, symbol, buyer, seller FROM trades;
Result:
INSERT INTO trades (symbol, buyer, seller) VALUES ('CITY', 58, 45), ('COAST', 45, NULL)
2 row(s) inserted
SELECT id, symbol, buyer, seller FROM trades
| id (LONG) | symbol (SYMBOL) | buyer (INT) | seller (INT) |
|---|---|---|---|
| 1 | CITY | 58 | 45 |
| 2 | COAST | 45 | NULL |
(2 rows)
Example: A broker that does not exist (shows an error)
Setup:
CREATE TABLE brokers (id INT PRIMARY KEY, name STRING NOT NULL);
CREATE TABLE trades (id LONG PRIMARY KEY AUTO, buyer INT NOT NULL REFERENCES brokers(id));
INSERT INTO brokers VALUES (58, 'Northgate Securities');
INSERT INTO trades (buyer) VALUES (99);
Result:
Error: column 'buyer' = '99' has no matching row in brokers(id)
Example: A broker with trades cannot be deleted (shows an error)
Setup:
CREATE TABLE brokers (id INT PRIMARY KEY, name STRING NOT NULL);
CREATE TABLE trades (id LONG PRIMARY KEY AUTO, buyer INT NOT NULL REFERENCES brokers(id));
INSERT INTO brokers VALUES (58, 'Northgate Securities');
INSERT INTO trades (buyer) VALUES (58);
DELETE FROM brokers WHERE id = 58;
Result:
Error: cannot remove brokers(id) = '58': table 'trades' column 'buyer' still refers to it (foreign key 'trades_buyer_fkey')
Example: Forum replies point to their parent post (same table)
Setup:
CREATE TABLE posts (id LONG PRIMARY KEY AUTO, parent_id LONG REFERENCES posts(id), author STRING NOT NULL, body STRING NOT NULL);
INSERT INTO posts (parent_id, author, body) VALUES (NULL, 'ram', 'Is CITY a good buy?');
INSERT INTO posts (parent_id, author, body) VALUES (1, 'sita', 'Yes, the dividend looks good.');
SELECT id, parent_id, author, body FROM posts;
Result:
INSERT INTO posts (parent_id, author, body) VALUES (NULL, 'ram', 'Is CITY a good buy?')
1 row(s) inserted
INSERT INTO posts (parent_id, author, body) VALUES (1, 'sita', 'Yes, the dividend looks good.')
1 row(s) inserted
SELECT id, parent_id, author, body FROM posts
| id (LONG) | parent_id (LONG) | author (STRING) | body (STRING) |
|---|---|---|---|
| 1 | NULL | ram | Is CITY a good buy? |
| 2 | 1 | sita | Yes, the dividend looks good. |
(2 rows)
Example: The parent column must be a key (shows an error)
Setup:
CREATE TABLE brokers (id INT PRIMARY KEY, name STRING NOT NULL);
CREATE TABLE trades (id LONG PRIMARY KEY AUTO, buyer_name STRING REFERENCES brokers(name));
Result:
Error: column 'buyer_name' of table 'trades' REFERENCES brokers(name), which is neither PRIMARY KEY nor UNIQUE; expected a key column so each value points at exactly one row
CHECK
Syntax:
column TYPE [CONSTRAINT name] CHECK (condition) -- after a column
[CONSTRAINT name] CHECK (condition) -- after the columns, for the table
ALTER TABLE table ADD [CONSTRAINT name] CHECK (condition)
ALTER TABLE table DROP CONSTRAINT name
A condition that every row must meet. A row where the condition is false is not written, and the statement fails. Use it for business rules: a balance is never negative, an amount is positive, a quarter is 1 to 4.
| Part | Rule |
|---|---|
condition | uses the stored columns of the same row, operators, CASE, IN, BETWEEN, LIKE and built-in functions (length(email) > 3) |
| not allowed | other rows, subqueries, aggregates (sum), window functions, saved functions, computed columns |
CONSTRAINT name | the name of the rule; without it KDB makes table_column_check (after a column) or table_check (for the table) |
NULL | a condition that is NULL passes: add NOT NULL when the value must be there |
| NaN | every comparison with a NaN (=, <, >=, BETWEEN, IN) is false here, and <> is true: CHECK (price > 0) refuses a NaN price, CHECK (price <> 0) does not. Write what a good value is, not what a bad one is. (A query compares a NaN as PostgreSQL does, bigger than every number: see DOUBLE.) |
- Returns:
CHECK constraint 'name' of table 't' failed: condition is false for the row id='1', balance='-4000.00'. The message names the rule, the table, the row key and the columns the rule uses. - Notes: table rules come after all columns. A column used by a
CHECKcannot be dropped;RENAME COLUMNrenames it inside the rule.SHOW CREATE TABLEshows every rule with its name. ADD and DROP CONSTRAINT change the rules later. A rule cannot compare with other rows (like "the sum of all balances is 0"); check that with a query or in a procedure.
Example: A withdrawal bigger than the balance is refused (shows an error)
Setup:
CREATE TABLE accounts (id INT PRIMARY KEY AUTO, owner STRING NOT NULL, balance DECIMAL(2) NOT NULL DEFAULT 0 CHECK (balance >= 0));
INSERT INTO accounts (owner, balance) VALUES ('Ram', 1000.00);
UPDATE accounts SET balance = balance - 5000 WHERE id = 1;
Result:
Error: CHECK constraint 'accounts_balance_check' of table 'accounts' failed: balance >= 0 is false for the row id='1', balance='-4000.00'
Example: Named rules for a ledger (shows an error)
Setup:
CREATE TABLE ledger (
id LONG PRIMARY KEY AUTO,
from_account INT NOT NULL,
to_account INT NOT NULL,
amount DECIMAL(2) NOT NULL,
CONSTRAINT positive_amount CHECK (amount > 0),
CONSTRAINT two_accounts CHECK (from_account != to_account)
);
INSERT INTO ledger (from_account, to_account, amount) VALUES (1, 2, 250.50);
INSERT INTO ledger (from_account, to_account, amount) VALUES (1, 1, 100.00);
Result:
1 row(s) inserted
Error: CHECK constraint 'two_accounts' of table 'ledger' failed: from_account != to_account is false for the row id='2', from_account='1', to_account='1'
Example: Quarter 1 to 4, and a CHECK lets NULL pass
Setup:
CREATE TABLE fundamentals (
symbol STRING NOT NULL,
quarter INT CHECK (quarter BETWEEN 1 AND 4),
eps DECIMAL(2)
);
INSERT INTO fundamentals VALUES ('CITY', 4, 12.10), ('COAST', NULL, 6.35);
SELECT symbol, quarter, eps FROM fundamentals;
SHOW CREATE TABLE fundamentals;
Result:
INSERT INTO fundamentals VALUES ('CITY', 4, 12.10), ('COAST', NULL, 6.35)
2 row(s) inserted
SELECT symbol, quarter, eps FROM fundamentals
| symbol (STRING) | quarter (INT) | eps (DECIMAL(2)) |
|---|---|---|
| CITY | 4 | 12.10 |
| COAST | NULL | 6.35 |
(2 rows)
SHOW CREATE TABLE fundamentals
| create_sql (STRING) |
|---|
| CREATE TABLE fundamentals ( symbol STRING NOT NULL, quarter INT, eps DECIMAL(2), CONSTRAINT fundamentals_quarter_check CHECK (quarter BETWEEN 1 AND 4) ) |
(1 row)
Example: A price rule: which values pass price > 0
Setup:
CREATE TABLE prices (symbol SYMBOL NOT NULL, price DOUBLE NOT NULL CHECK (price > 0));
INSERT INTO prices VALUES ('CITY', 527.1);
SELECT symbol, price FROM prices;
SELECT 527.1 > 0 AS good_price, 0 > 0 AS zero, -1 > 0 AS negative;
Result:
INSERT INTO prices VALUES ('CITY', 527.1)
1 row(s) inserted
SELECT symbol, price FROM prices
| symbol (SYMBOL) | price (DOUBLE) |
|---|---|
| CITY | 527.1 |
(1 row)
SELECT 527.1 > 0 AS good_price, 0 > 0 AS zero, -1 > 0 AS negative
| good_price (BOOL) | zero (BOOL) | negative (BOOL) |
|---|---|---|
| true | false | false |
(1 row)
Example: A NaN price is refused (shows an error)
Setup:
CREATE TABLE prices (symbol SYMBOL NOT NULL, price DOUBLE NOT NULL CHECK (price > 0));
INSERT INTO prices VALUES ('CITY', 'NaN');
Result:
Error: CHECK constraint 'prices_price_check' of table 'prices' failed: price > 0 is false for the row price='NaN'
Example: A zero price is refused (and -1 the same way) (shows an error)
Setup:
CREATE TABLE prices (symbol SYMBOL NOT NULL, price DOUBLE NOT NULL CHECK (price > 0));
INSERT INTO prices VALUES ('CITY', 0);
Result:
Error: CHECK constraint 'prices_price_check' of table 'prices' failed: price > 0 is false for the row price='0'
Example: A missing price is refused by NOT NULL (shows an error)
Setup:
CREATE TABLE prices (symbol SYMBOL NOT NULL, price DOUBLE NOT NULL CHECK (price > 0));
INSERT INTO prices VALUES ('CITY', NULL);
Result:
Error: column 'price' (DOUBLE): cannot be NULL
Example: A CHECK cannot look at other rows (shows an error)
CREATE TABLE accounts (id INT PRIMARY KEY, balance DECIMAL(2) CHECK (balance <= (SELECT max(balance) FROM accounts)));
Result:
Error: line 1, column 81: CHECK cannot use a subquery; expected a condition on the values of one row