PostgreSQL Partial Index
Index only the rows you need
🎯 What is a Partial Index?
A partial index indexes only rows that meet a specific condition. This saves space and improves performance by indexing only the data you actually query frequently.
-- Index only active users
CREATE INDEX idx_active_users
ON users(email) WHERE status = 'active';
Output:
CREATE INDEX
Key Partial Index Benefits
Space Saving
Smaller index size
-- Only index unpaid orders
CREATE INDEX idx_unpaid
ON orders(id) WHERE paid = false;
Performance
Faster queries and updates
-- Index recent records only
CREATE INDEX idx_recent
ON logs(created_at) WHERE created_at > '2024-01-01';
Targeted
Index specific subsets
-- Index only premium users
CREATE INDEX idx_premium
ON users(name) WHERE plan = 'premium';
Maintenance
Less index maintenance
-- Index non-deleted items
CREATE INDEX idx_active_products
ON products(name) WHERE deleted_at IS NULL;
🔹 Creating a Partial Index
Partial indexes use a WHERE clause to specify which rows to index. This is perfect when you frequently query a specific subset of data, like active users or pending orders.
-- Create orders table
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER,
status VARCHAR(20),
total DECIMAL(10,2)
);
-- Index only pending orders (most frequently queried)
CREATE INDEX idx_pending_orders
ON orders(user_id)
WHERE status = 'pending';
-- This query uses the index
SELECT * FROM orders
WHERE user_id = 123 AND status = 'pending';
Output:
Query uses the partial index for fast results
🔹 Partial Index for Active Records
A common use case is indexing only active or non-deleted records. This keeps your index small and fast since deleted records don't need to be indexed.
-- Create users table with soft delete
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255),
name VARCHAR(100),
deleted_at TIMESTAMP
);
-- Index only active (non-deleted) users
CREATE INDEX idx_active_users_email
ON users(email)
WHERE deleted_at IS NULL;
-- Fast lookup for active users
SELECT * FROM users
WHERE email = '[email protected]'
AND deleted_at IS NULL;
Output:
Index only contains active users, making it smaller and faster
🔹 Partial Unique Index
Combine partial indexes with uniqueness constraints. This allows unique values only within a subset, like ensuring active users have unique emails but allowing reuse after deletion.
-- Unique email only for active users
CREATE UNIQUE INDEX idx_unique_active_email
ON users(email)
WHERE deleted_at IS NULL;
-- First user with this email
INSERT INTO users (email, name)
VALUES ('[email protected]', 'John');
-- Soft delete the user
UPDATE users SET deleted_at = NOW()
WHERE email = '[email protected]';
-- Now this works - email can be reused after deletion!
INSERT INTO users (email, name)
VALUES ('[email protected]', 'John Smith');
Output:
INSERT 0 1
UPDATE 1
INSERT 0 1 (Success!)
🔹 Partial Index with Date Ranges
Index only recent data when older records are rarely queried. This is perfect for logs, events, or time-series data where you mostly query recent entries.
-- Create logs table
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
user_id INTEGER,
action VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
);
-- Index only logs from the last 30 days
CREATE INDEX idx_recent_logs
ON logs(user_id, created_at)
WHERE created_at > NOW() - INTERVAL '30 days';
-- Fast query for recent logs
SELECT * FROM logs
WHERE user_id = 123
AND created_at > NOW() - INTERVAL '30 days';
Output:
Index stays small by only including recent data
🔹 Partial Index for Boolean Columns
When a boolean column is mostly one value, index only the minority. For example, if most orders are paid, index only unpaid orders for faster processing.
-- Most orders are paid, few are unpaid
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER,
paid BOOLEAN DEFAULT false,
amount DECIMAL(10,2)
);
-- Index only unpaid orders (the minority)
CREATE INDEX idx_unpaid_orders
ON orders(user_id)
WHERE paid = false;
-- Fast query for unpaid orders
SELECT * FROM orders
WHERE user_id = 123 AND paid = false;
Output:
Small index for unpaid orders only
🔹 When to Use Partial Indexes
Partial indexes are most effective when you query a specific subset of data frequently. They reduce index size and maintenance while improving query performance on targeted data.
✅ Perfect for:
- Soft-deleted records (WHERE deleted_at IS NULL)
- Status-based queries (WHERE status = 'active')
- Recent data (WHERE created_at > date)
- Boolean minorities (WHERE paid = false)
- Specific categories (WHERE type = 'premium')
❌ Not ideal for:
- Queries that need all rows
- Conditions that match most rows
- Frequently changing conditions
- Complex WHERE clauses