- Systems' Newsletter
- Posts
- Database Constraints and Indexes
Database Constraints and Indexes
Your Data's Best Friends
These powerful features help maintain data integrity and optimize query performance. Let's dive in!
Understanding Constraints
Constraints are rules that protect your data's integrity. Think of them as guardrails that prevent invalid data from entering your database.
Why we need constraints
Without constraints, your database could end up with:
Missing required information
Duplicate records where uniqueness matters
Invalid relationships between tables
Logically impossible values
Types of Constraints
1. PRIMARY KEY
id SERIAL PRIMARY KEY
Ensures each row has a unique identifier
Cannot be NULL
Only one per table
Automatically creates an index
2. FOREIGN KEY
user_id INTEGER REFERENCES users(id)
Maintains relationships between tables
Ensures referential integrity
Prevents orphaned records
3. UNIQUE
email VARCHAR(100) UNIQUE NOT NULL
Ensures no duplicate values
Can be applied to single or multiple columns
Automatically creates an index
4. NOT NULL
username VARCHAR(100) NOT NULL
Ensures column always has a value
Prevents missing data
Common for required fields
5. CHECK
age INTEGER CHECK (age >= 0)
Validates data before insertion
Can use complex conditions
Ensures business rules
Practical Example: Products Table
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
sku VARCHAR(50) UNIQUE NOT NULL,
price DECIMAL(10, 2) CHECK (price >= 0),
stock INTEGER CHECK (stock >= 0),
category_id INTEGER REFERENCES categories(id)
);
This table ensures:
Each product has a unique identifier (PRIMARY KEY)
Products must have names (NOT NULL)
SKUs are unique across all products (UNIQUE)
Prices and stock can't be negative (CHECK)
Categories must exist in the categories table (FOREIGN KEY)
Remember: Constraints are your first line of defense against bad data. They're much more reliable than application-level validation because they're enforced by the database itself.
Understanding Indexes
Indexes are like the table of contents in a book - they help PostgreSQL find data quickly without scanning every row.
Why Use Indexes?
Without an Index
SELECT * FROM users WHERE email = '[email protected]';
Without an index, PostgreSQL performs a "sequential scan" - it checks every single row in the table until it finds the matching email. Imagine searching through 1 million users this way!
With an index
-- Create our high-speed lookup system
CREATE INDEX idx_users_email ON users(email);
When you create an index, PostgreSQL builds a sorted data structure (typically a B-tree) that makes finding specific values much faster - like jumping straight to the right page in a book.
Strategic Index Usage
✅ When to Create Indexes
Foreign Key Columns
Why: Speeds up table joins and referential integrity checks
Example:
user_id
in aposts
table
Frequently Searched Columns
Why: Drastically improves WHERE clause performance
Example: Email addresses, usernames, phone numbers
JOIN Condition Columns
Why: Makes database joins significantly faster
Example: Columns used to connect related tables
Sorting Columns
Why: Improves ORDER BY performance
Example: Timestamps for recent posts
❌ When to Avoid Indexes
Small Tables
Why: The overhead isn't worth it for tables with few rows
Rule of thumb: Tables with less than 1000 rows rarely need indexes
Low Selectivity Columns
Why: When a column has many duplicate values, indexes become less effective
Example: Boolean fields or status columns with few possible values
Frequently Updated Columns
Why: Each update must also update the index
Example: Counter fields or last_modified timestamps
Rarely Queried Columns
Why: Indexes add overhead to writes but provide no benefit
Example: Optional description fields rarely used in searches
Common Index Use Cases
Let's explore some common scenarios where indexes significantly improve query performance. For each case, we'll look at why you need an index and how to implement it effectively.
1. Pattern Matching in Text Fields
-- Email domain search
SELECT * FROM users
WHERE email LIKE '%@google.com';
-- Creating an appropriate index
CREATE INDEX idx_users_email ON users(email);
Why Index This?
Helps find users from specific email domains quickly
Common in user management and filtering systems
Without an index: Must check every user's email
With an index: Can quickly find matching patterns
2. Time-Based Queries
-- Recent orders
SELECT * FROM orders
WHERE created_at > NOW() - INTERVAL '24 hours';
-- Creating an appropriate index
CREATE INDEX idx_orders_created_at ON orders(created_at);
Why Index This?
Essential for finding recent records quickly
Common in dashboards and activity feeds
Without an index: Must scan all orders
With an index: Can jump straight to recent timestamps
Especially valuable for large tables with time-series data
3. Sorting and Limiting Results
-- Top products by price
SELECT * FROM products
ORDER BY price DESC LIMIT 10;
-- Creating an appropriate index
CREATE INDEX idx_products_price ON products(price DESC);
Why Index This?
Perfect for "Top N" queries
Common in product listings and rankings
Without an index: Must sort entire table
With index: Pre-sorted data makes this nearly instant
The DESC in the index matches our query's sort direction
Best Practices for Production
Index Creation Guidelines
Index columns you frequently search or sort by
Index foreign keys (PostgreSQL doesn't do this automatically!)
Don't over-index - each index makes writes slower
Practical Example: Products Table
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
sku VARCHAR(50) UNIQUE NOT NULL,
price DECIMAL(10, 2) CHECK (price >= 0),
stock INTEGER CHECK (stock >= 0),
category_id INTEGER REFERENCES categories(id)
);
-- Adding strategic indexes
CREATE INDEX idx_products_name ON products(name);
CREATE INDEX idx_products_category ON products(category_id);
Example Queries Using Indexes
-- These queries will use our indexes for better performance
SELECT * FROM products WHERE name ILIKE '%laptop%';
SELECT * FROM products WHERE category_id = 5;
Index Best Practices
Index Naming Convention
-- Format: idx_table_column
CREATE INDEX idx_users_email ON users(email);
Monitor Index Usage
-- Check index usage
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes;
Regular Maintenance
-- Rebuild index
REINDEX INDEX idx_users_email;
Performance Considerations
Trade-offs with Indexes
➕ Faster SELECT queries
➕ Improved JOIN performance
➖ Slower INSERT/UPDATE operations
➖ Additional disk space required
Monitoring and Maintenance
Regularly monitor index usage and performance
Rebuild or remove unused indexes
Balance between read performance and write overhead
Remember: Constraints ensure data integrity, while indexes improve read performance. Use both strategically to build robust and efficient database applications.