Constraints encode business rules

Database design · Essentials · 8 min

Application checks get bypassed by scripts, bugs and race conditions; constraints don't.

A schema is not just storage; it is a set of rules every row must obey. Declaring those rules as constraints means the database rejects bad data no matter which code path wrote it — the web app, a cron job, or a person in a SQL console.

  • NOT NULL for every column that must always have a value. Make nullable the exception you justify.
  • CHECK for row-level rules: quantity > 0, ends_at > starts_at, status IN ('new','paid','shipped').
  • UNIQUE for "only one of these": one account per email, one active cart per user (with a partial unique index in databases that support it).
  • Foreign keys so references always point to existing rows; choose ON DELETE behaviour (RESTRICT, CASCADE, SET NULL) deliberately.

Why not validate only in the application? Because two requests can both check "is this email free?" and both insert. Only a unique constraint closes that race: one insert succeeds, the other gets an error you can turn into a friendly message.

Constraints also document intent. A new developer reading CHECK (discount BETWEEN 0 AND 100) learns a business rule without hunting through code. The cost is small: a little work on each write, which is almost always worth it.

Example

CREATE TABLE booking (
    id        bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    room      int  NOT NULL,
    starts_at timestamptz NOT NULL,
    ends_at   timestamptz NOT NULL,
    status    text NOT NULL DEFAULT 'new'
              CHECK (status IN ('new', 'confirmed', 'cancelled')),
    CHECK (ends_at > starts_at)
);
INSERT INTO booking (room, starts_at, ends_at)
VALUES (101, '2026-05-01 14:00+00', '2026-05-01 12:00+00');
-- ERROR: new row for relation "booking" violates check constraint
--        "booking_check"

Remember

  • Constraints are enforced for every writer, not just your app
  • Only a UNIQUE constraint safely prevents concurrent duplicates
  • Default to NOT NULL; allow NULL only when 'unknown' is a real state

Watch out for

  • Relying on a SELECT-then-INSERT check in code to prevent duplicates.

Check yourself

  1. Two requests check that a username is free and both try to insert it at the same moment. What reliably stops the duplicate?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.postgresql.org/docs/current/ddl-constraints.html