Constraints: primary keys, UNIQUE, CHECK and DEFAULT
Make the database enforce your rules: primary and foreign keys, UNIQUE, CHECK, DEFAULT and NOT NULL. Name them, add them to existing tables, and choose what happens when a parent row is deleted.
| Constraint | Rule | Example |
|---|---|---|
NOT NULL | ||
PRIMARY KEY | ||
UNIQUE | ||
CHECK | ||
DEFAULT | ||
FOREIGN KEY |
A table with every kind
CONSTRAINT keyword. A common pattern is PK_Table, UQ_Table_Column, CK_Table_Column, DF_Table_Column and FK_Child_Parent. Error messages then tell you exactly which rule failed, and you can drop or change the constraint later by name.
CREATE TABLE dbo.Suppliers ( SupplierId int NOT NULL CONSTRAINT PK_Suppliers PRIMARY KEY, Name nvarchar(60) NOT NULL, Email nvarchar(100) NULL CONSTRAINT UQ_Suppliers_Email UNIQUE, Rating tinyint NOT NULL CONSTRAINT DF_Suppliers_Rating DEFAULT 3 CONSTRAINT CK_Suppliers_Rating CHECK (Rating BETWEEN 1 AND 5), CategoryId int NULL CONSTRAINT FK_Suppliers_Categories REFERENCES dbo.Categories (CategoryId)); INSERT INTO dbo.Suppliers (SupplierId, Name, Email, Rating, CategoryId)VALUES (1, N'Northern Timber', N'sales@ntimber.example', 4, 3); INSERT INTO dbo.Suppliers (SupplierId, Name)VALUES (2, N'Pennine Fixings'); SELECT * FROM dbo.Suppliers;SupplierId Name Email Rating CategoryId
---------- --------------- --------------------- ------ ----------
1 Northern Timber sales@ntimber.example 4 3
2 Pennine Fixings NULL 3 NULLAdding a constraint to an existing table
ALTER TABLE … ADD CONSTRAINT adds a rule later. SQL Server first checks the rows already there, and refuses if any break it. After that, every change is checked:
ALTER TABLE dbo.Products ADD CONSTRAINT CK_Products_Price CHECK (Price > 0);GOUPDATE dbo.Products SET Price = -5 WHERE ProductId = 1;Msg 547, Level 16, State 0, Line 1 The UPDATE statement conflicted with the CHECK constraint "CK_Products_Price". The conflict occurred in database "BuildMart", table "dbo.Products", column 'Price'.
What happens to children when a parent goes?
| ON DELETE … | Deleting a parent row… |
|---|---|
NO ACTION (default) | |
CASCADE | |
SET NULL | |
SET DEFAULT |
CREATE TABLE dbo.Quotes ( QuoteId int NOT NULL CONSTRAINT PK_Quotes PRIMARY KEY, CustomerId int NOT NULL);CREATE TABLE dbo.QuoteItems ( QuoteId int NOT NULL CONSTRAINT FK_QuoteItems_Quotes REFERENCES dbo.Quotes (QuoteId) ON DELETE CASCADE, ProductId int NOT NULL, Quantity int NOT NULL, CONSTRAINT PK_QuoteItems PRIMARY KEY (QuoteId, ProductId)); INSERT INTO dbo.Quotes (QuoteId, CustomerId) VALUES (1, 4), (2, 6);INSERT INTO dbo.QuoteItems (QuoteId, ProductId, Quantity) VALUES (1, 4, 30), (1, 5, 30), (2, 10, 3); DELETE FROM dbo.Quotes WHERE QuoteId = 1; SELECT * FROM dbo.QuoteItems;QuoteId ProductId Quantity
------- --------- --------
2 10 3Unique, but with many NULLs
CREATE TABLE dbo.Contacts ( ContactId int NOT NULL IDENTITY CONSTRAINT PK_Contacts PRIMARY KEY, Name nvarchar(60) NOT NULL, Email nvarchar(100) NULL);CREATE UNIQUE INDEX UX_Contacts_Email ON dbo.Contacts (Email) WHERE Email IS NOT NULL; INSERT INTO dbo.Contacts (Name, Email)VALUES (N'Ben', NULL), (N'Grace', NULL), (N'Asha', N'asha@example.com'); SELECT * FROM dbo.Contacts;ContactId Name Email
--------- ----- ----------------
1 Ben NULL
2 Grace NULL
3 Asha asha@example.comCK_Products_Stock that enforces this, then show that an UPDATE which takes 50 claw hammers from a stock of 40 is refused.
Show a hint
Show the solution
ALTER TABLE dbo.Products ADD CONSTRAINT CK_Products_Stock CHECK (Stock >= 0);GOUPDATE dbo.Products SET Stock = Stock - 50 WHERE ProductId = 1;Msg 547, Level 16, State 0, Line 1 The UPDATE statement conflicted with the CHECK constraint "CK_Products_Stock". The conflict occurred in database "BuildMart", table "dbo.Products", column 'Stock'.
Remember
Constraints are rules the database enforces on every change, from every program. Name them ( PK_,UQ_,CK_,DF_,FK_) so errors and later changes are clear.CHECK rejects only FALSE; NULL passes, so pair it with NOT NULL when a value is required. Foreign keys refuse orphan rows by default; ON DELETE CASCADE / SET NULL change that, use them deliberately. UNIQUE allows one NULL in SQL Server; a filtered unique index ( WHERE col IS NOT NULL) allows many.