Table Constraints
Table Constraints
Definition
A table constraint is written as its own separate clause inside CREATE TABLE (or added via a standalone ALTER TABLE ... ADD CONSTRAINT), rather than being attached inline to one column's definition. Syntactically it appears at the same level as a column definition, listed among the table's columns, but it names the column(s) it applies to explicitly inside parentheses rather than relying on inline placement.
Why Table Constraints Exist: The Multi-Column Requirement
The defining reason table-level syntax exists is that some rules inherently span more than one column, and there is no single column to visually "attach" them to:
sqlCREATE TABLE enrollments ( student_id INT, course_id INT, grade CHAR(2), enrolled_on DATE, completed_on DATE, PRIMARY KEY (student_id, course_id), -- composite key: 2 columns CHECK (completed_on IS NULL OR completed_on >= enrolled_on), -- 2 columns FOREIGN KEY (student_id) REFERENCES students(student_id) );
The composite PRIMARY KEY and the multi-column CHECK are only expressible this way — there is no column-level equivalent for either.
Full Syntax Forms
sqlCREATE TABLE t ( col1 INT, col2 INT, CONSTRAINT pk_t PRIMARY KEY (col1, col2), CONSTRAINT uq_t UNIQUE (col1), CONSTRAINT chk_t CHECK (col1 < col2), CONSTRAINT fk_t FOREIGN KEY (col2) REFERENCES other_table(id) );
Every constraint type — PRIMARY KEY, UNIQUE, CHECK, FOREIGN KEY — has a valid table-level form. NOT NULL is the sole exception: it has no table-level form at all, since "not null" is inherently a per-column property with no meaningful multi-column phrasing.
Table Constraints on Single Columns — Also Legal
A table constraint doesn't strictly require multiple columns; it's legal (if slightly more verbose) to write a single-column rule at table level too:
sqlCREATE TABLE employees ( emp_id INT, salary NUMERIC(10,2), CONSTRAINT chk_salary_positive CHECK (salary > 0) -- table-level, but only touches 1 column );
This is functionally identical to writing CHECK (salary > 0) inline on the salary column — the choice here is purely stylistic (some teams standardize on always using table-level clauses grouped at the bottom of the CREATE TABLE for readability, even for single-column rules).
Adding Table Constraints After Creation
sqlALTER TABLE enrollments ADD CONSTRAINT chk_dates CHECK (completed_on IS NULL OR completed_on >= enrolled_on);
ALTER TABLE ... ADD CONSTRAINT is inherently a "table constraint" operation — there is no way to retroactively splice a constraint into the middle of an existing column's inline definition; anything added later is structurally a table-level addition even if it only touches one column.
Edge Cases
- Table constraints are always comma-separated siblings of column definitions inside the
CREATE TABLEparentheses — order relative to columns doesn't matter for evaluation, only for readability. - A single
CREATE TABLEcan mix column-level and table-level constraints freely in the same statement. - Some style guides mandate table-level syntax for every constraint (even single-column ones) specifically to keep constraint management centralized and easy to scan/audit in one place near the bottom of the DDL.
Key Takeaways / Q&A
Q: Which constraint type has no table-level form at all? A: NOT NULL — it is exclusively a column-level, per-column property.
Q: Is it valid to write a single-column CHECK at table level? A: Yes, it's legal and purely a stylistic choice, functionally identical to the column-level version.
Q: Why is a composite PRIMARY KEY unavoidably a table constraint? A: Because it spans two or more columns, and there's no single column definition it could be attached to inline.