Constraints
NOT NULL and CHECK constraints
Applications often validate data before sending it to the database.
For example, an application might check that a product has a price and that the price is greater than zero.
But important data rules should also be enforced by the database itself.
PostgreSQL provides constraints for this.
A constraint defines a rule that the data in a table must satisfy. If an INSERT or UPDATE would violate the rule, PostgreSQL rejects it.
In this chapter, we'll look at two constraints used to validate values within a row:
NOT NULLCHECK
NOT NULL
A column normally allows NULL unless it has been defined with a NOT NULL constraint.
Our products.price column is NOT NULL.
Try inserting a product without a price:
1234567891011121314INSERT INTO products ( id, sku, name, category, price)VALUES ( 900001, 'SKU-CONSTRAINT-001', 'Constraint Demo Product', 'office', NULL);PostgreSQL rejects the insert because price is not allowed to contain NULL.
NOT NULL is useful when a value is required for every row.
For example:
1price NUMERIC(10, 2) NOT NULLmeans that every product must have a price.
CHECK constraints
NOT NULL tells PostgreSQL that a value must exist.
But it doesn't tell PostgreSQL which non-NULL values are valid.
For example, this value is not NULL:
1-50.00but a negative product price probably doesn't make sense.
For rules like this, we can use a CHECK constraint.
Add one to the products table:
123ALTER TABLE productsADD CONSTRAINT products_price_positiveCHECK (price > 0);The constraint requires this expression to be satisfied:
1price > 0Now try inserting a product with a negative price:
1234567891011121314INSERT INTO products ( id, sku, name, category, price)VALUES ( 900001, 'SKU-CONSTRAINT-001', 'Constraint Demo Product', 'office', -10.00);PostgreSQL rejects the insert because the value violates products_price_positive.
A simplified view looks like this:
The database now enforces the rule regardless of where the write came from.
NOT NULL and CHECK solve different problems
Consider:
123price NUMERIC(10, 2) NOT NULL CHECK (price > 0)The two constraints enforce different rules.
NOT NULL means:
1A price must exist.CHECK (price > 0) means:
1When a price exists, it must be greater than zero.Using both gives us the rule we actually want:
1Every product must have a positive price.CHECK and NULL
There is an important detail about CHECK constraints.
A CHECK constraint is considered satisfied when its expression evaluates to either TRUE or NULL.
For example:
1CHECK (price > 0)does not by itself prevent:
1price = NULLbecause:
1NULL > 0doesn't evaluate to FALSE. It evaluates to an unknown result.
If the value must also be present, use NOT NULL:
123price NUMERIC(10, 2) NOT NULL CHECK (price > 0)This is why NOT NULL and CHECK are often used together.
CHECK constraints can use multiple columns
A CHECK constraint can also validate a relationship between columns in the same row.
Our orders table contains:
12placed_atshipped_atA reasonable rule might be that an order cannot be shipped before it was placed.
That could be expressed as:
1234CHECK ( shipped_at IS NULL OR shipped_at >= placed_at)If shipped_at is NULL, the order hasn't been shipped yet.
Otherwise, the shipping time must be equal to or later than the placement time.
This kind of rule belongs to the row as a whole rather than to one column in isolation.
Column and table constraints
A constraint that relates only to one column can often be written directly beside the column definition:
1234CREATE TABLE example_products ( id INTEGER PRIMARY KEY, price NUMERIC(10, 2) NOT NULL CHECK (price > 0));A constraint can also be written separately as a table constraint:
123456789CREATE TABLE example_orders ( id INTEGER PRIMARY KEY, placed_at TIMESTAMPTZ NOT NULL, shipped_at TIMESTAMPTZ, CHECK ( shipped_at IS NULL OR shipped_at >= placed_at ));Table constraints are especially useful when the rule involves multiple columns.
Naming constraints
We gave our constraint a name:
1CONSTRAINT products_price_positiveNaming constraints makes them easier to identify when PostgreSQL reports a violation and easier to modify later.
For example, we can remove it by name:
12ALTER TABLE productsDROP CONSTRAINT products_price_positive;Constraints protect the database, not just one application
Imagine the application contains this validation:
12if price <= 0: reject requestThat protects writes that go through that particular code path.
But data might also be written by:
another API
an admin tool
a script
a migration
direct SQL
application code containing a bug
A database constraint protects the rule regardless of which code performs the write.
Application validation is still useful because it can provide better error messages to users.
The database constraint is the final guarantee that invalid data cannot be stored.
Keep CHECK constraints about the row
CHECK constraints are intended to enforce conditions based on the row being inserted or updated.
For example:
1CHECK (price > 0)or:
1CHECK (shipped_at IS NULL OR shipped_at >= placed_at)are appropriate because the values being checked belong to the same row.
Rules that depend on data in other rows or other tables are usually better enforced using mechanisms designed for those relationships, such as UNIQUE or foreign key constraints.
Before continuing, remove the constraint added in this chapter:
12ALTER TABLE productsDROP CONSTRAINT products_price_positive;The important point is:
Constraints let PostgreSQL enforce important data rules even when application code fails to do so.