Use when designing database schemas, implementing repository patterns, writing optimized queries, managing migrations, or working with indexes and transactions for SQL/NoSQL databases.
Use when designing database schemas, implementing repository patterns, writing optimized queries, managing migrations, or working with indexes and transactions for SQL/NoSQL databases.
disable-model-invocation
true
Database Patterns
Overview
Database design and access patterns for relational and NoSQL databases.
Schema Design
Normalization Levels
Level
Description
Use Case
1NF
Atomic values, no repeating groups
Base requirement
2NF
No partial dependencies
Most applications
3NF
No transitive dependencies
OLTP systems
Denormalized
Redundant data for reads
Read-heavy, analytics
Common Table Patterns
-- Users tableCREATE TABLE users (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUENOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR() ,
status () ,
created_at ,
updated_at
);
users deleted_at ;
INDEX idx_users_deleted users(deleted_at) deleted_at ;
users created_by UUID users(id);
users updated_by UUID users(id);
255
NOT NULL
VARCHAR
20
DEFAULT
'active'
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
-- Soft delete pattern
ALTER TABLE
ADD
COLUMN
TIMESTAMP
NULL
CREATE
ON
WHERE
IS
NULL
-- Audit columns
ALTER TABLE
ADD
COLUMN
REFERENCES
ALTER TABLE
ADD
COLUMN
REFERENCES
Relationships
-- One-to-ManyCREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID NOT NULLREFERENCES users(id),
total DECIMAL(10,2) NOT NULL,
created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP
);
CREATE INDEX idx_orders_user ON orders(user_id);
-- Many-to-ManyCREATE TABLE order_products (
order_id UUID REFERENCES orders(id) ONDELETE CASCADE,
product_id UUID REFERENCES products(id) ONDELETE CASCADE,
quantity INTNOT NULL,
price DECIMAL(10,2) NOT NULL,
PRIMARY KEY (order_id, product_id)
);
-- Self-referential (tree/hierarchy)CREATE TABLE categories (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL,
parent_id UUID REFERENCES categories(id)
);
CREATE INDEX idx_categories_parent ON categories(parent_id);
Indexing Strategies
Index Types
Type
Use Case
Example
B-tree
Range, equality
Most columns
Hash
Equality only
Exact matches
GIN
Arrays, JSON, full-text
JSONB, text search
GiST
Geometric, range types
PostGIS, IP ranges
Index Guidelines
-- Primary key (automatic)CREATE TABLE users (id UUID PRIMARY KEY);
-- Foreign keysCREATE INDEX idx_orders_user ON orders(user_id);
-- Frequent filtersCREATE INDEX idx_users_status ON users(status);
-- Composite for multi-column queriesCREATE INDEX idx_orders_user_status ON orders(user_id, status);
-- Partial index for common queriesCREATE INDEX idx_active_users ON users(email) WHERE status ='active';
-- Expression indexCREATE INDEX idx_users_email_lower ON users(LOWER(email));
When NOT to Index
Small tables (< 1000 rows)
Frequently updated columns
Low cardinality columns
Columns rarely used in WHERE
Query Patterns
Efficient Queries
-- Use specific columns, not *SELECT id, name, email FROM users WHERE id = $1;
-- Limit resultsSELECT*FROM users ORDERBY created_at DESC LIMIT 20;
-- Exists vs COUNTSELECTEXISTS(SELECT1FROM users WHERE email = $1);
-- Batch insertsINSERT INTO users (name, email) VALUES
('User 1', 'user1@example.com'),
('User 2', 'user2@example.com'),
('User 3', 'user3@example.com');
Pagination
-- Offset pagination (simple but slow for large offsets)SELECT*FROM users ORDERBY created_at DESC LIMIT 20OFFSET100;
-- Cursor pagination (better performance)SELECT*FROM users
WHERE created_at < $cursorORDERBY created_at DESC
LIMIT 20;
-- Keyset pagination with tie-breakerSELECT*FROM users
WHERE (created_at, id) < ($cursor_time, $cursor_id)
ORDERBY created_at DESC, id DESC
LIMIT 20;
Common Query Patterns
-- Upsert (INSERT or UPDATE)INSERT INTO users (email, name)
VALUES ($1, $2)
ON CONFLICT (email)
DO UPDATESET name = EXCLUDED.name, updated_at = NOW();
-- Soft deleteUPDATE users SET deleted_at = NOW() WHERE id = $1;
SELECT*FROM users WHERE deleted_at ISNULL;
-- Lock for update (prevent race conditions)SELECT*FROM accounts WHERE id = $1FORUPDATE;
-- Bulk updateUPDATE orders SET status ='shipped'WHERE id =ANY($1::uuid[]);