SQL guide · Constraints
SQL CHECK Constraint: Enforce Rules on Column Values
A CHECK constraint attaches a condition to a column or a table, and every inserted or updated row must satisfy it. If the condition evaluates to false, the statement fails. This lets you encode business rules directly in the schema: prices cannot be negative, a discount must be between 0 and 100, a status must be one of a short list, or an end date must not be before a start date.
Because the rule lives in the database, it applies no matter which application, script or analyst writes the data. That is a major advantage over validation in application code alone, which is easy to bypass or forget. The condition can use comparison operators, AND, OR, IN, BETWEEN, LIKE and most scalar functions, and a table-level check can compare several columns in the same row.
One detail matters a lot: a check passes when the condition evaluates to unknown, which is what happens when a compared column is NULL. For example CHECK (price > 0) still allows a null price. If the column must also be present, combine the check with NOT NULL. Checks cannot normally reference other tables or run subqueries, so use a foreign key or trigger for cross-table rules.
Basic syntax
CREATE TABLE table_name (
column1 data_type CHECK (condition),
column2 data_type,
CONSTRAINT chk_name CHECK (condition_using_columns)
);
ALTER TABLE table_name
ADD CONSTRAINT chk_name CHECK (condition);condition is any boolean expression over columns of the same row. A constraint written beside a column normally refers to that column, while a named table-level constraint can refer to several. Naming the constraint gives you a clear error message and an easy way to drop it later.
SQL CHECK examples
Restrict a numeric range
Products must have a positive price and a stock quantity of zero or more. Inserting a negative price produces a constraint violation naming the rule.
CREATE TABLE products (
product_id INT PRIMARY KEY,
name VARCHAR(120) NOT NULL,
price NUMERIC(10,2) NOT NULL CHECK (price > 0),
stock_qty INT NOT NULL,
CONSTRAINT chk_stock_nonnegative CHECK (stock_qty >= 0)
);Limit a column to allowed values
An IN list works like an enumeration. Only the listed statuses can be stored, which prevents typos such as shiped from creeping into reports.
CREATE TABLE orders (
order_id INT PRIMARY KEY,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
CONSTRAINT chk_order_status
CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'))
);Compare two columns in the same row
A table-level check can enforce relationships between columns. This one makes sure a promotion cannot end before it starts. It is checked on every insert and update.
CREATE TABLE promotions (
promo_id INT PRIMARY KEY,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
CONSTRAINT chk_promo_dates CHECK (end_date >= start_date)
);Add a check to an existing table
Existing rows are validated when you add the constraint, so a single bad row blocks the change. Find the offenders first, fix them, then add the rule.
SELECT * FROM employees WHERE salary < 0;
ALTER TABLE employees
ADD CONSTRAINT chk_salary_positive CHECK (salary >= 0);Where SQL dialects differ
PostgreSQL: Fully supported, and checks may use immutable functions. ADD CONSTRAINT ... NOT VALID followed by VALIDATE CONSTRAINT avoids a long lock on big tables. Drop with ALTER TABLE t DROP CONSTRAINT chk_name.
MySQL: MySQL parsed but silently ignored CHECK constraints before 8.0.16. From 8.0.16 they are enforced. Use ALTER TABLE t DROP CHECK chk_name (or DROP CONSTRAINT in newer 8.0 releases). Checks cannot use stored functions or subqueries.
SQL Server: Supported with ADD CONSTRAINT. Constraints added with WITH NOCHECK skip existing rows and are marked not trusted, which can prevent the optimizer from using them. User-defined functions are allowed in checks but can hurt performance.
SQLite: Enforced when declared in CREATE TABLE. SQLite cannot add a check to an existing table with ALTER TABLE, so you must rebuild the table. Checks are skipped when you run the pragma ignore_check_constraints.
Oracle: Supports CHECK inline or through ALTER TABLE ... ADD CONSTRAINT. Conditions cannot use subqueries or non-deterministic functions such as SYSDATE. Constraints can be created with ENABLE NOVALIDATE to skip existing rows.
Common mistakes
- Expecting
CHECK (col > 0)to reject nulls. A null makes the condition unknown, and unknown passes, so also declare the columnNOT NULL. - Writing a rule that references another table. Checks only see the current row; use a foreign key for lookup lists or a trigger for complex rules.
- Running on an old MySQL version and assuming the constraint works. Before 8.0.16 it had no effect at all.
- Leaving constraints unnamed. Auto-generated names make error messages cryptic and make
DROP CONSTRAINTscripts hard to write. - Hard-coding values that change often, such as a list of countries. Put those in a lookup table with a foreign key instead.
Related SQL guides
Need help writing the SQL?
Describe your tables and the result you want, then review the generated query against your database before running it.
Try the AI SQL generator