21 NOT NULL and DEFAULT
NOT NULL and DEFAULT are the two most useful per-column rules. NOT NULL says the column always has a value — every INSERT and UPDATE must provide one (or rely on a default). DEFAULT expr provides the value when the column is omitted. Together, they make schemas easier to reason about: you never have to write IF column IS NULL in application code if the column is NOT NULL.
What you’ll learn
- Enforcing required values with
NOT NULL - Supplying defaults with
DEFAULT expr - Volatile vs constant defaults and what that means at scale
- Combining
NOT NULLandDEFAULTfor “always has a value” - Removing or changing defaults later
Example
NOT NULL is one of the cheapest, highest-impact constraints you can add — it eliminates an entire class of bugs (forgotten null checks, broken aggregates, surprising joins). Mark columns NOT NULL whenever the application logic guarantees a value.
DEFAULT is evaluated at insert time, not at table creation time. DEFAULT current_timestamp runs current_timestamp once per inserted row, returning the transaction start time. DEFAULT gen_random_uuid() generates a fresh UUID for each row.
Defaults that are constants (DEFAULT 0, DEFAULT 'pending') are fast to add to existing tables — PostgreSQL 11+ stores the constant default in metadata without rewriting the table. Nonvolatile defaults, including the STABLE expression current_timestamp, can also use the fast path. Volatile defaults such as random() and clock_timestamp() require a value for each existing row and rewrite the table.
Run the example
To run:
Common pitfalls
- Treating
''(empty string) as “missing” — pick one and be consistent. Most teams useNULLfor “no value” and reserve''for “intentionally blank.” Mixing the two leads to half-broken queries forever. - Defaults are not automatically reapplied on
UPDATE. To “reset to default,” useUPDATE t SET col = DEFAULT WHERE .... - Adding
NOT NULLto an existing column that may have NULLs fails. Backfill the column first, thenSET NOT NULL. INSERT INTO t DEFAULT VALUESis your friend when every column has a default — it lets the database fill in everything.
A common pattern is created_at timestamptz NOT NULL DEFAULT current_timestamp paired with updated_at timestamptz NOT NULL DEFAULT current_timestamp. The created_at is set on INSERT and never changes; an updated_at needs a trigger (covered later) to bump on every UPDATE.
Try INSERT INTO tasks_example (id) VALUES (3); — the NOT NULL on title should reject it. Then INSERT INTO tasks_example (id, title, priority) VALUES (3, 'X', 9); to see the CHECK reject an out-of-range priority.
Downloads
SQL file: not-null-and-default.sql · All lesson files · Setup guide