NOT NULL, DEFAULT, UNIQUE and CHECK
The four constraints that guard a single table. What each one enforces, how NULL interacts with UNIQUE and CHECK, and why the database is the right place for these rules.
- SQL Basics
- DDL
- DML
- SELECT
- WHERE
- Functions
- NULL and Logic
- Aggregate Functions
- GROUP BY
- JOIN
- Subqueries
- Set Operations
- CTEs
- Constraints
- Keys
- Relationships
- Database Design
- Normalisation
- Views
- Window Functions
- Advanced SQL
- Procedures and Functions
- Triggers
- Temporary Tables
- Transactions
- Isolation and Locking
- Indexes
- Query Performance
- SQL Security
- SQL Dialects
- Practical SQL
Concept
A constraint is a rule the database enforces on every write, from every client, forever. Application validation can be bypassed by a script, a migration or a second application; a constraint cannot.
| Constraint | Guarantees |
|---|---|
NOT NULL | The column always has a value |
DEFAULT | A value is supplied when the statement omits the column |
UNIQUE | No two rows share the same value |
CHECK | Every row satisfies a boolean condition |
Syntax
CREATE TABLE products (
id INT NOT NULL,
name VARCHAR(80) NOT NULL,
sku VARCHAR(20) NOT NULL,
category VARCHAR(40) NOT NULL DEFAULT 'general',
price DECIMAL(10,2) NOT NULL,
discount DECIMAL(5,2) NULL,
CONSTRAINT pk_products PRIMARY KEY (id),
CONSTRAINT uq_products_sku UNIQUE (sku),
CONSTRAINT ck_products_price CHECK (price > 0),
CONSTRAINT ck_products_disc CHECK (discount IS NULL OR (discount >= 0 AND discount <= 100))
);Example
-- Each of these is rejected by a different constraint
INSERT INTO products (id, name, sku, price) VALUES (1, NULL, 'SKU1', 100); -- NOT NULL
INSERT INTO products (id, name, sku, price) VALUES (2, 'Pen', 'SKU1', 100); -- UNIQUE
INSERT INTO products (id, name, sku, price) VALUES (3, 'Pen', 'SKU3', -5); -- CHECK
-- Omitting a defaulted column is fine
INSERT INTO products (id, name, sku, price) VALUES (4, 'Pen', 'SKU4', 100);
-- category is 'general'Explanation
UNIQUE and NULL
-- Most engines allow MANY NULLs in a UNIQUE column
CREATE TABLE contacts (
id INT PRIMARY KEY,
email VARCHAR(120) NULL,
CONSTRAINT uq_contacts_email UNIQUE (email)
);
INSERT INTO contacts VALUES (1, NULL);
INSERT INTO contacts VALUES (2, NULL); -- allowed in MySQL, PostgreSQL, Oracle, SQLiteBecause NULL = NULL is unknown, two NULLs are not "the same value" and the constraint is not violated. SQL Server is the exception: its UNIQUE constraint permits exactly one NULL. If you need "at most one row without a value", add NOT NULL or a filtered index.
CHECK and NULL
A CHECK passes when the condition is TRUE or UNKNOWN. So CHECK (price > 0) happily accepts a NULL price. That is why the price column above is also declared NOT NULL - the two constraints do different jobs and are both needed.
DEFAULT
status VARCHAR(10) NOT NULL DEFAULT 'active',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
quantity INT NOT NULL DEFAULT 0A default applies only when the column is omitted from the INSERT. Explicitly inserting NULL into a defaulted NOT NULL column is an error, not a fallback.
Important rules
NOT NULLandCHECKare independent; aCHECKdoes not implyNOT NULL.UNIQUEusually permits multipleNULLs. SQL Server permits one.- A
UNIQUEconstraint is backed by an index, so it also speeds up lookups on that column. - A composite
UNIQUE (a, b)constrains the combination, not each column separately. CHECKis enforced from MySQL 8.0.16 and MariaDB 10.2. Earlier MySQL versions parsed and silently ignored it.- A
CHECKcan only reference columns of the same row - no subqueries, no other tables.
Common mistakes
- Expecting
CHECK (salary > 0)to also preventNULL. - Adding
UNIQUEto a column that already contains duplicates - clean the data first. - Relying on application code alone, then discovering bad rows inserted by an import script.
- Writing
DEFAULTand then explicitly passingNULLfrom the application layer. - Assuming
CHECKis enforced on an old MySQL 5.7 server.
Best practices
- Make
NOT NULLthe default choice; justify every nullable column. - Name constraints so error messages identify the rule:
ck_products_price. - Pair
CHECKwithNOT NULLwhenever a value is mandatory. - Enforce rules in the database and validate in the application - the database for correctness, the application for a good error message.
- Use a
CHECKor a lookup table for status columns instead of free text.
Practice
- Add constraints to
ordersso the total can never be negative and the status must be one of four known values. - Explain why
UNIQUE(email)on a nullable column still allows several employees with no email. - Write a composite
UNIQUEthat stops the same employee being assigned to the same project twice.