Imagine a bank where a customer can have a negative account balance for a "Savings" account, or an e-commerce site where an order is placed for a product that doesn't exist. These are nightmares for developers and business owners alike.
In the world of databases, SQL Constraints are the invisible walls that prevent these nightmares. They are the rules you define on your tables to ensure that the data being entered is accurate, reliable, and consistent.
A Real Business Scenario: Checkout Data You Can Actually Trust
Suppose an online store allows orders to be created from the web app, an admin panel, and a background import job from a marketplace. Even if the application code is careful, mistakes still happen:
- a duplicate order number can slip in during a retry
- a line item can point to a product that was deleted
- a negative quantity can appear because of a bad CSV import
Constraints matter because they protect the data no matter which path writes it. That is why they are not just "nice to have" rules for clean schemas. They are operational safeguards.
Why Constraints Matter
Without constraints, your database is just a "bucket" of data. With constraints, it becomes a "source of truth." Constraints help you:
- Prevent duplicate data (e.g., two users with the same email).
- Maintain relationships (e.g., ensuring an order always belongs to a valid customer).
- Enforce business rules (e.g., a "price" column must always be greater than zero).
1. PRIMARY KEY: The Unique Anchor
Every table should have a PRIMARY KEY. It uniquely identifies each record in the table. It must contain unique values and cannot contain NULL values.
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50)
);
2. NOT NULL: No Empty Spaces
The NOT NULL constraint ensures that a column cannot have a NULL value. Use this for essential information like a user's name or an order date.
3. UNIQUE: Preventing Duplicates
While a primary key is unique, sometimes you need other columns to be unique too—like an email address or a social security number. The UNIQUE constraint ensures all values in a column are different.
4. FOREIGN KEY: Linking Tables
A FOREIGN KEY is a column (or collection of columns) in one table that refers to the PRIMARY KEY in another table. This ensures Referential Integrity.
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
If you try to delete a user who has active orders, the database will stop you (unless you've specified ON DELETE CASCADE), preventing "orphan" records.
A Common Mistake: Validating Only in Application Code
Many teams say, "We already validate that in the app," and skip the database constraint. That works until a second code path appears:
- an internal script writes directly to the table
- a migration backfills data with a bug
- another service imports rows without the same validation logic
Application validation is useful for user experience. Database constraints are what make the rule universal.
Another subtle mistake is assuming every database enforces constraints the same way. For example, SQLite has FOREIGN KEY support, but it must be enabled in the connection settings. The syntax may look portable while the enforcement behavior is not.
5. CHECK: Enforcing Business Logic
The CHECK constraint allows you to specify a condition that must be met for the data to be valid. This is incredibly powerful for enforcing domain-specific rules.
6. DEFAULT: Safe Fallbacks
The DEFAULT constraint provides a default value for a column when none is specified during an INSERT. This is great for setting timestamps or initial status values.
CREATE TABLE posts (
id INT PRIMARY KEY,
title VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Strategy: When to Use Which?
| Goal | Constraint |
|---|---|
| Identify rows uniquely | PRIMARY KEY |
| Link two tables | FOREIGN KEY |
| Prevent empty values | NOT NULL |
| Prevent duplicate values | UNIQUE |
| Enforce range/list rules | CHECK |
| Set a fallback value | DEFAULT |
Boundary and Performance Notes
Constraints are usually cheap compared with the cost of bad data, but they still have practical implications:
PRIMARY KEYandUNIQUEconstraints typically create or rely on indexes, which means inserts and updates have more work to do- large backfills can fail late if invalid historical rows already exist
- foreign keys improve integrity, but they also mean parent deletes and child inserts must respect relationship checks
- some business rules are too cross-row or too cross-table to express cleanly with a simple
CHECK
The right pattern is to use constraints for durable invariants and keep more procedural or workflow-specific logic in application code, triggers, or review processes where needed.
When NOT to Reach for a Constraint First
Do not force every rule into a constraint when:
- the rule depends on external systems or current time in a non-deterministic way
- the logic spans many rows and would be awkward or expensive to encode declaratively
- the business policy changes frequently and needs a staged rollout
In those cases, you may still keep a looser database safeguard while handling the richer policy in application logic.
Official References
- PostgreSQL constraints documentation for the standard behavior and tradeoffs of declarative constraints.
- SQLite foreign key documentation for SQLite-specific enforcement details and caveats.
- SQLite CREATE TABLE documentation for
CHECK,DEFAULT, and column-level constraint syntax.
Tool Workflow
Use tools when constraints should be reviewed as part of a real schema workflow
Constraints are easiest to trust when you can inspect the table shape, compare schema revisions, and see how relationships and keys fit together across the whole model.
Related Articles
- SQL Table Relationships: One-to-Many and Many-to-Many for the foreign-key patterns constraints are meant to protect.
- Database Normalization Explained for the table-splitting decisions that often create the need for stronger integrity rules.
- SQL Data Types Deep Dive for the column-level choices that work together with constraints to keep data valid.
Conclusion
Constraints are your first line of defense against data corruption. While it’s tempting to handle all validation in your application code, putting it in the database ensures that no matter how the data is accessed—via your app, an admin tool, or a manual script—the rules are ALWAYS enforced.
Design your tables so the database can reject impossible states early. That discipline pays off most when multiple apps, scripts, and humans are all writing to the same data.
Practice Challenge
Try creating a students table that requires a GPA to be between 0.0 and 4.0 and provides a default enrollment_year of 2024. Use the concepts we learned above!