| name | databases |
| description | PostgreSQL and MongoDB patterns - queries, indexing, performance optimization, migrations. |
Databases
Load this skill when working with PostgreSQL or MongoDB.
Database Selection
| Use PostgreSQL When | Use MongoDB When |
|---|
| Complex relationships | Document-oriented data |
| ACID transactions needed | Flexible schema |
| Complex queries/joins | Horizontal scaling |
| Data integrity critical | Rapid prototyping |
| Reporting/analytics | Nested/hierarchical data |
PostgreSQL
Essential Queries
INSERT INTO users (email, name)
VALUES ('test@example.com', 'Test')
RETURNING id, created_at;
INSERT INTO users (email, name)
VALUES ('test@example.com', 'Updated')
ON CONFLICT (email)
DO UPDATE SET name = EXCLUDED.name, updated_at = NOW();
SELECT * FROM posts
WHERE created_at < $1
ORDER BY created_at DESC
LIMIT 20;
SELECT * FROM articles
WHERE to_tsvector('english', title || ' ' || body)
@@ plainto_tsquery('english', $1);
Indexing Strategy
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_active_users ON users(email)
WHERE deleted_at IS NULL;
CREATE INDEX idx_posts_user_date ON posts(user_id, created_at DESC);
CREATE INDEX idx_posts_search ON posts
USING GIN(to_tsvector('english', title || ' ' || body));
CREATE INDEX idx_users_email_name ON users(email) INCLUDE (name);
Performance Analysis
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'test@example.com';
SELECT query, calls, mean_time, total_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;
SELECT schemaname, tablename, n_live_tup, n_dead_tup,
last_vacuum, last_autovacuum
FROM pg_stat_user_tables;
SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
WHERE schemaname = 'public';
Transactions
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
BEGIN;
INSERT INTO orders (user_id, total) VALUES (1, 100);
SAVEPOINT before_items;
INSERT INTO order_items (order_id, product_id) VALUES (1, 999);
ROLLBACK TO before_items;
COMMIT;
MongoDB
Essential Operations
db.users.insertOne({
email: "test@example.com",
name: "Test",
createdAt: new Date()
})
db.users.updateOne(
{ email: "test@example.com" },
{ $set: { name: "Updated" }, $setOnInsert: { createdAt: new Date() }},
{ upsert: true }
)
db.orders.aggregate([
{ $match: { status: "completed" }},
{ $group: { _id: "$userId", total: { $sum: "$amount" }}},
{ $sort: { total: -1 }},
{ $limit: 10 }
])
db.posts.find({ createdAt: { $lt: lastSeenDate }})
.sort({ createdAt: -1 })
.limit(20)
Indexing
db.users.createIndex({ email: 1 }, { unique: true })
db.posts.createIndex({ userId: 1, createdAt: -1 })
db.articles.createIndex({ title: "text", body: "text" })
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
db.users.createIndex(
{ email: 1 },
{ partialFilterExpression: { deletedAt: null }}
)
Performance Analysis
db.users.find({ email: "test@example.com" }).explain("executionStats")
db.currentOp({ "active": true, "secs_running": { $gt: 5 }})
db.users.stats()
db.users.aggregate([{ $indexStats: {} }])
Common Patterns
Soft Delete
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP;
SELECT * FROM users WHERE deleted_at IS NULL;
UPDATE users SET deleted_at = NOW() WHERE id = $1;
db.users.updateOne({ _id: id }, { $set: { deletedAt: new Date() }})
db.users.find({ deletedAt: null })
Pagination Comparison
| Method | Pros | Cons |
|---|
| Offset/Limit | Simple, random access | Slow on large offsets |
| Cursor-based | Consistent, fast | No random access |
| Keyset | Very fast | Requires unique, sequential key |
SELECT * FROM posts
WHERE (created_at, id) < ($last_created_at, $last_id)
ORDER BY created_at DESC, id DESC
LIMIT 20;
Optimistic Locking
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = $1 AND version = $2;
db.products.updateOne(
{ _id: id, version: currentVersion },
{ $inc: { stock: -1, version: 1 }}
)
Migration Best Practices
- Always reversible - Write down AND up migrations
- Small changes - One logical change per migration
- Test on copy - Never migrate production without testing
- Avoid locks - Use concurrent index creation
- Backfill separately - Don't hold transactions for data migration
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
ALTER TABLE users ADD COLUMN phone TEXT;
Quick Reference
PostgreSQL
| Need | Command |
|---|
| Connect | psql -h host -U user -d database |
| List tables | \dt |
| Describe table | \d tablename |
| List indexes | \di |
| Query plan | EXPLAIN ANALYZE query |
MongoDB
| Need | Command |
|---|
| Connect | mongosh "mongodb://host/db" |
| List collections | show collections |
| Collection stats | db.collection.stats() |
| Indexes | db.collection.getIndexes() |
| Query plan | .explain("executionStats") |