Using CHECK constraint in PostgreSQL: A complete guide

Updated: January 4, 2024 By: Guest Contributor Post a comment

Introduction

The CHECK constraint in PostgreSQL ensures that all values in a column satisfy certain conditions. It’s an integral part of creating robust and reliable databases, enforcing field-level data integrity.

What is a CHECK Constraint?

A CHECK constraint is a rule that specifies the acceptable data values that can be held by a column or a combination of columns in a PostgreSQL table. Whenever a row is inserted or updated, the constraint checks the validity of the input data against the defined condition. If the condition returns true, the operation proceeds; if false, the operation is rejected.

Basic Usage of CHECK Constraints

Let’s begin with the simplest usage of a CHECK constraint by ensuring that a person’s age is always over 18. Suppose we have a table named users.

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  age INT,
  CHECK (age > 18)
);

This constraint will disallow any insert or update operation where the age is below 19.

Creating Conditional CHECK Constraints

PostgreSQL also allows for more complex, conditional constraints. For example, suppose we want age to be non-negative and user_status to be either ‘active’ or ‘inactive’.

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  age INT CHECK (age >= 0),
  user_status VARCHAR(10) CHECK (user_status IN ('active', 'inactive'))
);

In the above case, we’ve created two separate constraints directly tied to individual columns.

Applying Constraints to Existing Tables

If you wish to add a CHECK constraint to an existing table, you can use the ALTER TABLE statement.

ALTER TABLE users ADD CHECK (age < 65);

The above alters the users table to add a condition that nobody is older than 65 years.

Using Constraints on Multiple Columns

Constraints aren’t limited to a single column. They can span multiple columns to create more complex validations. For example, if we wanted to ensure that our users’ salary field makes sense with the age, we might use:

ALTER TABLE users ADD CHECK ((age < 22 AND salary < 50000) OR (age >= 22));

This enforces that a user younger than 22 years cannot have a salary above $50,000.

Building Complex CHECK Constraints

PostgreSQL offers tremendous flexibility, so you can include various operators and functions within a CHECK constraint expression. In the next example, we ensure that the start date is before the end date for a project:

CREATE TABLE projects (
  id SERIAL PRIMARY KEY,
  project_name VARCHAR(255) NOT NULL,
  start_date DATE NOT NULL,
  end_date DATE NOT NULL,
  CHECK (start_date < end_date)
);

Here you have to ensure that the start and end dates make logical sense.

Defining Named CHECK Constraints

By naming our constraints, we can easily identify them for future modifications or error messages. Use the CONSTRAINT keyword followed by the desired name.

ALTER TABLE projects
  ADD CONSTRAINT valid_dates
  CHECK (start_date < end_date);

This constraint now has a clear name: valid_dates.

Checking Constraints with Subqueries

For more advanced constraints, PostgreSQL allows subqueries within the check condition, but with certain restrictions, as subqueries must be immutable, meaning that they cannot modify database state. Let’s assume we want to ensure that a department budget doesn’t exceed a certain threshold:

CREATE TABLE department_budgets (
  department_id INT,
  budget DECIMAL(10,2),
  CHECK (
    (SELECT SUM(budget) FROM department_budgets) < 1000000
  )
);

This constraint would check that the total sum of all departments’ budgets does not exceed one million units of currency.

Handling Violations

When a CHECK constraint violation occurs, Postgres will raise an error, stopping the transaction. You can catch these errors in your application to provide user feedback or take corrective action.

Custom Error Messages

While PostgreSQL does not support the direct inclusion of custom error messages within a CHECK constraint declaration, you can create custom error handling in your client application or use database functions to mimic this behavior within your schema.

Disabling and Enabling Constraints

Sometimes you need to disable constraints temporarily, such as when loading data that may not fit the constraints yet. To disable and re-enable a CHECK constraint, you can use:

ALTER TABLE users DISABLE TRIGGER ALL;
-- Bulk-insert or update data
ALTER TABLE users ENABLE TRIGGER ALL;

However, be cautious and ensure data integrity is maintained when re-enabling constraints.

Performance Considerations

Having multiple or complex CHECK constraints can affect insert and update operation performance. Evaluate the necessity of each constraint and check if indexes or other PostgreSQL features might offer more efficient alternatives.

Conclusion

CHECK constraints enhance the integrity of your PostgreSQL databases by enforcing custom rules at the column level. They offer a way to programmatically ensure that data adheres to specific business rules and logic. Successfully integrating CHECK constraints into your schema liberates you from manual checks and related errors, giving you confidence in your data’s quality.