Integrity Constraints

Imagine a banking database. A transfer statement debits account `A-101` for $500. But that account does not exist in the `Account` table. Or consider a `Loan` record listing a `borrower_ssn`. That SSN matches no one in the `Customer` table. Without rules preventing these situations, a database silently accumulates nonsense. Money is owed by nobody. Orders are placed by phantom customers. Employees are assigned to departments deleted years ago. Integrity constraints are the mechanism that stops this from happening. An integrity constraint is a condition declared as part of the database schema. Every valid instance of the database must satisfy it. Constraints are not an afterthought bolted onto a working schema. They are part of what the schema means. A relation schema together with its constraints tells you more than data shape. It also tells you which combinations of values are meaningful in the modeled world. This is why constraint design belongs squarely in Data Modeling. It also belongs in the broader discipline of schema design, alongside Relations, Attributes, and Keys. This article covers four constraint families. Every relational system enforces them, or is expected to. These are: domain constraints (what values an attribute may hold), key constraints (uniqueness of tuples), entity-integrity constraints (primary keys cannot be unknown), and referential-integrity constraints (foreign keys must point to something that exists). Together these four rules capture much of the "common sense" a database needs. They let it reject bad data automatically. No application writer has to re-implement the same checks by hand. By the end you will be able to state each constraint formally. You will trace how a DBMS checks it on insert, update, or delete. You will understand the referential-integrity violation policies (`CASCADE`, `SET NULL`, `RESTRICT`). You will also see why these constraints are the seed. From them grow both Normalization Motivation and Anomalies and Mapping ER to Relations. A schema without correct constraints cannot be normalized correctly. This is because normalization is defined in terms of the functional dependencies and keys that constraints encode.

Key takeaways
  • Integrity constraints are conditions on the schema that every legal database instance must satisfy at all times.
  • Domain constraints restrict each attribute to a set of atomic, typed values (e.g., `age INTEGER CHECK (age >= 0)`).
  • Key constraints require that a chosen attribute set uniquely identifies every tuple in a relation.
  • Entity integrity forbids any attribute of the primary key from being `NULL`.
  • Referential integrity (foreign keys) requires that a referencing value must match an existing value in the referenced relation, or be `NULL`.
  • Violation policies — `RESTRICT`, `CASCADE`, `SET NULL` — determine what happens to referencing tuples when a referenced tuple is deleted or updated.
The core insight

Integrity constraints turn "this schema should model X" into an enforceable, checkable rule that the DBMS — not the application — guarantees on every write.

The idea

A relation schema specifies attribute names and domains, but by itself it permits any set of tuples over those domains, including tuples that make no sense in the application. An integrity constraint is a predicate over database states, declared once in the schema, that the DBMS checks and enforces on every insertion, deletion, and update. A database instance is called legal or consistent if it satisfies all declared constraints; the DBMS's job is to guarantee that only legal instances ever exist, by rejecting or repairing any operation that would produce an illegal one. Four constraint families form the foundation of relational integrity. A domain constraint restricts an attribute to a specific set of atomic values — an integer range, a fixed-length string, an enumerated set, or a `NOT NULL` requirement — and is checked independently, tuple by tuple, attribute by attribute. A key constraint (or uniqueness constraint) designates one or more attribute sets, called candidate keys, such that no two distinct tuples in the relation may agree on all attributes of that set; one candidate key is designated the primary key. Entity integrity is the rule that no attribute participating in the primary key may be `NULL` — because a `NULL` primary key value cannot uniquely identify a tuple, defeating the very purpose of a key. Referential integrity, enforced through foreign keys, requires that whenever a relation R1R_1 has an attribute set α\alpha declared as a foreign key referencing a candidate key of relation R2R_2, every non-null value of α\alpha in R1R_1 must appear as a value of that candidate key in R2R_2. These four families interact in a strict hierarchy of scope. Domain constraints are the narrowest, checked per-attribute-value. Key constraints operate within a single relation, comparing tuples against each other. Entity integrity is a special case tying keys to nullability. Referential integrity is the widest in scope: it is the only one of the four that spans two relations, and it is precisely the mechanism that lets a normalized schema — split into many small relations by Normalization Motivation and Anomalies — be reassembled correctly via joins, because the foreign key tells the query processor exactly which tuples in one relation "belong with" which tuples in another.

How it works

Domain constraints. A domain is declared with a base type (`INTEGER`, `VARCHAR(n)`, `DATE`, …) optionally refined by a `CHECK` clause and a nullability rule. The check is purely local: for a single attribute value vv being written into attribute AA of relation RR, the DBMS evaluates the domain predicate DA(v)D_A(v) and rejects the write if it is false. Because the check needs no access to other tuples or other relations, it costs O(1)O(1) per value (or O(length of value)O(\text{length of value}) for string-pattern checks) and is applied to every attribute of every inserted or updated tuple.

Key constraints. A candidate key K⊆RK \subseteq R requires: (1) uniqueness — no two distinct tuples t1≠t2t_1 \neq t_2 in any legal instance of RR have t1[K]=t2[K]t_1[K] = t_2[K]; and (2) irreducibility (minimality) — no proper subset of KK also satisfies uniqueness. Uniqueness is what the DBMS actively enforces on writes; minimality is a design property checked once, at schema-creation time, by the designer (typically derived from functional-dependency analysis, foreshadowing normalization). Enforcement uses an index (commonly a B+-tree or hash index automatically built on KK) so that a new tuple's key value can be looked up in O(log⁡n)O(\log n) or O(1)O(1) average time rather than scanning all nn existing tuples.

Entity integrity. This is a nullability restriction layered on top of the primary key: for the designated primary key KpK_p, every attribute A∈KpA \in K_p must be non-null in every tuple. It is checked at the same time and by the same mechanism as the key-uniqueness check, since a `NULL` value would need special-case comparison semantics (`NULL` is not equal to `NULL` under standard three-valued logic) that would make uniqueness itself ambiguous.

Referential integrity. Let R1R_1 (the referencing relation) declare a foreign key α\alpha that references a candidate key β\beta of R2R_2 (the referenced relation). The constraint requires:

Πα(σα is not null(R1))⊆Πβ(R2)\Pi_\alpha(\sigma_{\alpha \text{ is not null}}(R_1)) \subseteq \Pi_\beta(R_2)

in relational-algebra terms: every non-null projection of R1R_1 onto α\alpha must appear among the projections of R2R_2 onto β\beta. This is checked on three kinds of operations: insertion into R1R_1 (the new α\alpha value must exist in R2R_2, or be null), update of α\alpha in R1R_1 (same check), and, crucially, deletion from R2R_2 or update of β\beta in R2R_2 — because removing or changing a referenced key value could orphan tuples in R1R_1 that still point to it. This last case is where a violation policy must be chosen: `NO ACTION`/`RESTRICT` (reject the delete/update if referencing tuples exist), `CASCADE` (propagate the delete/update into R1R_1), or `SET NULL` / `SET DEFAULT` (null out or default the referencing attribute in R1R_1).

Step-by-step enforcement algorithm

``` function VALIDATE_WRITE(operation, tuple_or_key, relation R):

# operation ∈ {INSERT, DELETE, UPDATE} if operation is INSERT or UPDATE: for each attribute A of R: v = new_value(tuple_or_key, A) if not DOMAIN_CHECK(A, v): reject "domain constraint violated on attribute A" for each attribute A in PrimaryKey(R): if new_value(tuple_or_key, A) is NULL: reject "entity integrity violated: primary key attribute A is null" for each candidate key K of R: if EXISTS tuple t' in R (t' != tuple_or_key) with t'[K] = tuple_or_key[K]: reject "key constraint violated: duplicate key value" for each foreign key alpha of R referencing (beta, R2): v = tuple_or_key[alpha] if v is not NULL and v NOT IN Project_beta(R2): reject "referential integrity violated: no matching key in R2" commit tuple if operation is DELETE or (UPDATE and key attributes changed): old_key_value = tuple_or_key[PrimaryKey(R)] # or the referenced candidate key for each relation R1 with foreign key referencing R: matches = { t1 in R1 : t1[foreign_key] = old_key_value } if matches is not empty: switch POLICY(R1, R): case RESTRICT: reject "referential integrity violated: referencing tuples exist" case CASCADE: if operation is DELETE: for t1 in matches: DELETE t1 from R1 # recursive call else: # UPDATE for t1 in matches: UPDATE t1.foreign_key = new_key_value case SET_NULL: for t1 in matches: UPDATE t1.foreign_key = NULL commit delete/update ``` The order matters: domain checks run first because they are the cheapest and most local (fail fast on a malformed value before doing any lookup). Key and entity-integrity checks run next since they only require a lookup within RR itself. Referential-integrity checks run last for inserts/updates because they require touching a second relation, and the deletion-side checks recurse across the whole graph of foreign-key dependencies, potentially touching many relations in a single `CASCADE`.

Figure
Schema graph: primary keys, candidate keys, and a foreign-key edge with its violation policy
Customer customer_id (PK) ssn (candidate key) name address domain: name NOT NULL Loan loan_id (PK) borrower_id (FK → Customer) amount domain: amount > 0 borrower_id may be NULL references ON DELETE of Customer row: RESTRICT → reject delete if a Loan still references it CASCADE → delete the Loans too SET NULL → set borrower_id = NULL

Complexity & correctness

Domain constraint checking costs O(1)O(1) time per attribute value for range/type checks, or O(m)O(m) for a pattern/regex check of length mm; space overhead is O(1)O(1) beyond storing the value itself, since no auxiliary structure is needed. This is checked on every insert and every update to that attribute — O(a)O(a) total per tuple write, where aa is the number of constrained attributes.

Key constraint checking, done naively, would require scanning all nn existing tuples to look for a duplicate — O(n)O(n) per write. Real systems avoid this by maintaining an index on every declared key: a B+-tree gives O(log⁡Bn)O(\log_B n) I/Os for the uniqueness lookup (BB the tree fan-out), and a hash index gives expected O(1)O(1). Space cost is the index itself, typically O(n)O(n) additional storage. This is a direct efficiency argument for why keys are always backed by indexes in practice, not just a modeling nicety.

Entity integrity adds no asymptotic cost — it is a single extra `NULL` test folded into the same tuple-write pass that checks key uniqueness, still O(1)O(1) per attribute.

Referential integrity on insert/update into the referencing relation R1R_1 requires one lookup of the foreign-key value in the referenced relation's key index: O(log⁡Bn2)O(\log_B n_2) with a B+-tree over R2R_2's n2n_2 tuples, again assuming (as SQL requires) that the referenced attribute set is indexed because it must be a candidate key of R2R_2. On delete/update of the referenced relation R2R_2, the DBMS must find all matching tuples in R1R_1; this requires an index on the foreign-key attribute in R1R_1 (not mandated by the relational model but essential in practice), giving O(log⁡Bn1+k)O(\log_B n_1 + k) where kk is the number of matching tuples, versus O(n1)O(n_1) with a full scan. `CASCADE` deletes can recurse through a chain of foreign keys; if the reference graph has depth dd and fan-out ff at each level, cascading touches up to O(fd)O(f^d) tuples in the worst case, which is why cyclic or deep cascade chains are a known operational hazard (see pitfalls below).

Correctness argument. The claim to prove is: if every write operation is validated by the algorithm above before being committed, then every reachable database state is legal (satisfies domain, key, entity-integrity, and referential-integrity constraints). This is an inductive invariant argument over the sequence of committed operations.

  • Base case. The empty database (no tuples) vacuously satisfies all four constraint types, since there are no tuples to violate uniqueness, no null primary keys to find, and no dangling foreign-key values to detect.
  • Inductive step. Assume the database is legal after kk committed operations. Consider operation k+1k+1. The algorithm checks, before committing, exactly the predicate that defines each constraint: domain membership for every new value, non-null-ness for every primary-key attribute, absence of a duplicate key value among existing plus new tuples, and (for inserts/updates) presence of the foreign-key value in the referenced key's projection, or (for deletes/updates of the referenced side) absence of any orphaned referencing tuple once the chosen policy is applied. Since the pre-state was legal by the inductive hypothesis, and the algorithm only permits the operation to commit when the post-state predicate holds, the post-state is legal.
  • Referential integrity's cross-relation subtlety is handled correctly because the algorithm treats `CASCADE`/`SET NULL` as part of the same atomic operation: the referencing tuples are fixed up (deleted, updated, or nulled) before the operation is considered committed, so the post-state never transiently violates the constraint from the perspective of any query that runs after the transaction completes. This is precisely why the DBMS must implement cascades as part of the same transaction as the triggering delete/update, not as an asynchronous cleanup — an important tie-in to Transactions and atomicity.

Honest tradeoffs. Enforcing constraints is not free: every insert pays the cost of index lookups for each key and each foreign key, and `CASCADE` chains can turn one delete into an unbounded cascade of deletes across the whole schema, which is a real risk to availability and to unintended data loss. Some high-throughput systems deliberately relax enforcement — deferring checks to end-of-transaction (`DEFERRABLE` constraints), or dropping foreign keys altogether in favor of application-level checks — trading correctness guarantees for write latency. This tradeoff is a live design decision in large-scale systems and is discussed further under Normalization Motivation and Anomalies, since denormalized schemas often shed foreign keys precisely to avoid this overhead.

Worked example

Consider a simplified banking schema: ``` Branch(branch_name PK, branch_city, assets) Customer(customer_id PK, ssn UNIQUE, name NOT NULL) Account(account_number PK, branch_name FK→Branch, balance CHECK(balance >= 0)) Depositor(customer_id FK→Customer, account_number FK→Account, PRIMARY KEY(customer_id, account_number)) ``` Trace the following sequence of operations, showing which constraint (if any) is checked and whether the write is accepted:

#OperationConstraint checkedResult
1`INSERT Branch('Downtown', 'Brooklyn', 9000000)`domain (`assets` is numeric); key (`branch_name` unique in empty table)accepted
2`INSERT Account('A-101', 'Downtown', 500)`domain (`balance >= 0` ✓); key (`account_number` unique); referential integrity: `'Downtown'` ∈ `Π branch_name(Branch)` ✓accepted
3`INSERT Account('A-102', 'Uptown', 300)`referential integrity: `'Uptown'` ∉ `Π branch_name(Branch)`rejected — dangling foreign key
4`INSERT Customer(NULL, '111-22-3333', 'Hayes')`entity integrity: `customer_id` (PK) is `NULL`rejected
5`INSERT Customer('C-1', '111-22-3333', 'Hayes')`key/domain checks passaccepted
6`INSERT Customer('C-2', '111-22-3333', 'Turner')`key constraint: `ssn = '111-22-3333'` already used by `C-1`rejected — duplicate candidate key
7`INSERT Depositor('C-1', 'A-101')`referential integrity: `'C-1'` ∈ `Customer`, `'A-101'` ∈ `Account`; entity integrity: neither PK component nullaccepted
8`DELETE Branch('Downtown', …)` with policy `RESTRICT`referencing tuple `Account('A-101', 'Downtown', …)` existsrejected
9`DELETE Branch('Downtown', …)` with policy `CASCADE`deletes `Account('A-101', …)`; that deletion in turn cascades to `Depositor('C-1','A-101')`accepted, 3 tuples removed total
10`DELETE Branch('Downtown', …)` with policy `SET NULL``Account('A-101').branch_name` set to `NULL`; `Depositor` untouched (its FK is to `Account`, not `Branch`)accepted, 1 tuple modified

Step 3 shows referential integrity failing in the insert direction (child references a non-existent parent). Steps 8–10 show the same underlying deletion attempt behaving completely differently depending on the declared violation policy — the constraint is fixed, but its enforcement consequence is a schema-design choice. Step 9's cascade illustrates the recursive fan-out from the complexity discussion: deleting one `Branch` tuple transitively removed a tuple in `Account` and a tuple in `Depositor`, two "hops" down the reference graph.

Common pitfalls & traps

PitfallWhy it happensFix
Treating `NULL` foreign key as a violationConfusing "foreign key must reference an existing tuple" with "foreign key must be non-null"A `NULL` foreign key is exempt from referential integrity by default; require `NOT NULL` explicitly if every tuple must reference something
Forgetting entity integrity applies component-wise to composite keysAssuming only the "whole key" needs checkingEvery attribute in a composite primary key must individually be non-null, not just the tuple as a whole
Using `CASCADE` on a chain the designer didn't fully traceCascades compose silently across every FK edge in the schema, not just the one being editedDraw (or query) the full reference graph before adding `CASCADE`; consider `RESTRICT` as the safer default
Believing a foreign key must reference a primary keyThe SQL standard only requires the referenced side to be a candidate key (unique, non-null), not necessarily the designated primary keyCheck which attribute set is declared `UNIQUE`/`PRIMARY KEY` on the referenced relation before assuming
Assuming domain constraints catch semantic errors`CHECK (age >= 0)` only rejects impossible values, not merely wrong onesDomain constraints bound plausibility, not correctness; genuine data-entry errors still need application-level or statistical checks
Ignoring the cost of key/FK indexes at scaleEvery insert now pays index-maintenance cost, which is easy to overlook when reasoning only about read queriesBudget for index maintenance in write-heavy workloads; consider deferred constraint checking within a transaction if bulk-loading

Pioneers & history

Integrity constraints are inseparable from the relational model itself: E. F. Codd introduced the notion that a database schema should carry declarative rules — checked by the system, not by application code — as a core part of his 1970 relational model, and he sharpened this further in later work explicitly proposing entity integrity and referential integrity as the two fundamental rules every relational database must obey.

Origins · 1 minEdgar F. Codd and how this was discovered
EF
Edgar F. Codd
1923–2003 · IBM Research, relational database theory

Codd trained as a mathematician and worked at IBM, where he grew frustrated with the rigid, pointer-based hierarchical and network database systems of the 1960s. His 1970 paper "A Relational Model of Data for Large Shared Data Banks" proposed representing all data uniformly as relations (tables) manipulated through set-based operations, with constraints as first-class, declarative parts of the schema rather than procedural checks buried in application code. Codd continued to refine the model through the 1970s and 1980s, formalizing entity integrity and referential integrity as the two non-negotiable rules of a properly designed relational database, and later articulated twelve rules for what should count as a genuinely relational system.

History
1970
— Codd publishes the relational model, proposing set-based tables in place of pointer-based hierarchical/network structures.
1979
— Codd's later papers formalize entity integrity and referential integrity as explicit, named rules.
1986
— The SQL standard (SQL-86) is ratified, giving `PRIMARY KEY`, `UNIQUE`, and early integrity syntax a standardized form.
1989
— SQL-89 extends integrity-constraint support, including basic referential-integrity clauses.
1992
— SQL-92 formalizes `FOREIGN KEY`, `REFERENCES`, `ON DELETE`/`ON UPDATE` actions (`CASCADE`, `SET NULL`, `RESTRICT`) still used today.

Where this matters

Every production relational database — PostgreSQL, MySQL/InnoDB, Oracle, SQL Server, SQLite — implements domain, key, entity-integrity, and referential-integrity constraints as core engine features, typically backed by B+-tree indexes for efficient enforcement. Application frameworks (Django ORM, Rails ActiveRecord, Hibernate/JPA) expose these constraints as schema annotations (`unique=True`, `null=False`, `ForeignKey(on_delete=CASCADE)`) that generate the underlying SQL `DDL`. Data warehouses and ETL pipelines rely on referential-integrity checks to catch upstream data-quality problems before a broken foreign key propagates into a report. Financial and healthcare systems in particular depend on entity integrity and referential integrity as an automated, unbypassable line of defense — an accounting system simply cannot allow a debit against a nonexistent account, and a hospital system cannot allow a prescription record to reference a patient ID that was never created. These same constraints are exactly what makes it possible to reconstruct a fully normalized schema — split across many relations by Normalization Motivation and Anomalies — back into a coherent whole via joins guided by foreign keys, and they are the concrete artifact that Mapping ER to Relations produces when it turns entity-relationship diagrams into working table definitions.

Ask the tutor: How would you redesign the violation policy for a schema where a `CASCADE` delete on `Customer` currently risks wiping out years of `Order` history?

Is a candidate key the same thing as a primary key?

No. A candidate key is any minimal attribute set that uniquely identifies tuples in a relation — a relation can have several. The primary key is the one candidate key the designer chooses as the "official" identifier used by foreign keys and default indexing. All other candidate keys are typically still enforced as `UNIQUE` constraints. For example, `Customer` might have both `customer_id` and `ssn` as candidate keys; declaring `customer_id` primary does not weaken the uniqueness requirement on `ssn` — it simply determines which one other relations reference by convention.

Can a foreign key reference a non-primary-key attribute?

Yes, as long as that attribute is declared `UNIQUE` (a candidate key) in the referenced relation — the SQL standard requires the referenced side to be a unique, non-null-enforced attribute set, not necessarily the designated primary key. This matters because a relation can have multiple candidate keys, and different foreign keys elsewhere in the schema might legitimately reference different ones, depending on which identifier makes semantic sense in that relationship.

What happens if I try to insert a tuple whose foreign-key value is NULL?

By default, referential integrity does not apply to `NULL` foreign-key values — the constraint is defined only over non-null projections, so a `NULL` foreign key trivially satisfies it. This models genuinely optional relationships, such as an `Employee.manager_id` that is `NULL` for the company's top executive. If every tuple must reference something, the schema should additionally declare that foreign-key attribute `NOT NULL`, layering a domain-style constraint on top of the referential one.

Why does the order of checks in the enforcement algorithm matter?

Cheaper, more local checks should run first so invalid writes fail fast without wasted work. Domain checks touch only the incoming value; key/entity-integrity checks touch only the local relation (via an index lookup); referential-integrity checks require consulting a second relation and, for deletes, potentially cascading through several more. Running domain checks first means a malformed value never reaches an expensive cross-relation lookup, which matters for throughput even though all checks are logically independent conditions on the same commit.

What is the difference between CASCADE, SET NULL, and RESTRICT?

They are policies for what happens to referencing tuples when a referenced tuple is deleted or its key is updated. `RESTRICT` (or `NO ACTION`) refuses the delete/update outright if any referencing tuple exists, preserving data but requiring explicit manual cleanup. `CASCADE` propagates the operation, deleting or updating referencing tuples automatically, which is convenient but can silently remove far more data than expected across a whole reference graph. `SET NULL` keeps the referencing tuple but clears its foreign-key value, appropriate only when the relationship is optional and the referencing attribute permits `NULL`.

Do integrity constraints replace the need for application-level validation?

No. Integrity constraints guarantee structural correctness — types, uniqueness, existence of referenced rows — but they cannot capture arbitrary business logic, such as "a loan amount cannot exceed 5 times the customer's declared annual income," which requires comparing values across relations in ways beyond a simple `CHECK` clause, or requires external computation. Such rules are typically enforced with database triggers, stored procedures, or application-layer validation, layered on top of — not instead of — the four constraint families described here.

How do integrity constraints relate to normalization?

Normalization is defined in terms of functional dependencies, and functional dependencies are exactly the formal generalization of "a key determines the rest of the tuple." Correctly identifying candidate keys — a prerequisite for entity integrity and for choosing sensible foreign keys — is the first analytical step that Normalization Motivation and Anomalies builds on to detect and remove update, insertion, and deletion anomalies. A schema with poorly chosen or missing keys cannot be normalized correctly, because the normal-form definitions themselves reference the key structure directly.

Before this

Where this leads