query-optimization
Patterns for optimizing database query performance
What this skill does
# Query Optimization Skill Patterns for improving database query performance. ## Understanding Query Performance ### EXPLAIN ANALYZE ```sql -- PostgreSQL EXPLAIN ANALYZE SELECT u.*, COUNT(p.id) as post_count FROM users u LEFT JOIN posts p ON p.author_id = u.id WHERE u.role = 'ADMIN' GROUP BY u.id; -- Key metrics to watch: -- - Seq Scan vs Index Scan -- - Actual rows vs Estimated rows -- - Sort operations -- - Nested loops vs Hash joins ``` ### Query Plan Reading ``` Seq Scan -- Full table scan (often bad) Index Scan -- Using index (good) Index Only Scan -- All data from index (best) Bitmap Scan -- Multiple index matches Hash Join -- Good for large joins Nested Loop -- Good for small inner table Sort -- May use disk if large ``` ## Indexing Strategies ### When to Index ```sql -- Index: Foreign keys (always) CREATE INDEX idx_posts_author_id ON posts(author_id); -- Index: Frequently filtered columns CREATE INDEX idx_users_role ON users(role); -- Index: Columns in ORDER BY CREATE INDEX idx_posts_created_at ON posts(created_at DESC); -- Index: Columns in JOIN conditions CREATE INDEX idx_comments_post_id ON comments(post_id); ``` ### Composite Indexes ```sql -- Order matters! Most selective first CREATE INDEX idx_posts_status_date ON posts(published, published_at DESC); -- Covers queries like: SELECT * FROM posts WHERE published = true ORDER BY published_at DESC; SELECT * FROM posts WHERE published = true AND published_at > '2024-01-01'; -- Does NOT help: SELECT * FROM posts WHERE published_at > '2024-01-01'; -- Needs leading column ``` ### Partial Indexes ```sql -- Only index active records CREATE INDEX idx_active_users_email ON users(email) WHERE deleted_at IS NULL; -- Only index specific values CREATE INDEX idx_pending_orders ON orders(created_at) WHERE status = 'pending'; ``` ### Covering Indexes ```sql -- Include all needed columns to avoid table lookup CREATE INDEX idx_users_covering ON users(email) INCLUDE (name, role); -- Query can be satisfied entirely from index: SELECT email, name, role FROM users WHERE email = '[email protected]'; ``` ## Query Patterns ### Avoid N+1 Queries ```typescript // Bad: N+1 queries const users = await prisma.user.findMany(); for (const user of users) { user.posts = await prisma.post.findMany({ where: { authorId: user.id }, }); } // Good: Eager loading const users = await prisma.user.findMany({ include: { posts: true }, }); // Good: Separate batch query const users = await prisma.user.findMany(); const posts = await prisma.post.findMany({ where: { authorId: { in: users.map(u => u.id) } }, }); ``` ### Efficient Pagination ```typescript // Offset pagination (slow for large offsets) const users = await prisma.user.findMany({ skip: 10000, take: 20, orderBy: { createdAt: 'desc' }, }); // Cursor pagination (consistent performance) const users = await prisma.user.findMany({ take: 20, cursor: { id: lastUserId }, orderBy: { createdAt: 'desc' }, }); // Keyset pagination (fastest for sorted data) const users = await prisma.user.findMany({ where: { createdAt: { lt: lastCreatedAt }, }, take: 20, orderBy: { createdAt: 'desc' }, }); ``` ### Selective Field Loading ```typescript // Bad: Load everything const users = await prisma.user.findMany(); // Good: Only needed fields const users = await prisma.user.findMany({ select: { id: true, name: true, email: true, }, }); ``` ### Bulk Operations ```typescript // Bad: Individual inserts for (const item of items) { await prisma.item.create({ data: item }); } // Good: Batch insert await prisma.item.createMany({ data: items, skipDuplicates: true, }); // Good: Batch update await prisma.item.updateMany({ where: { status: 'pending' }, data: { status: 'processed' }, }); ``` ## Common Optimizations ### Avoid SELECT * ```sql -- Bad SELECT * FROM users WHERE id = 1; -- Good SELECT id, name, email FROM users WHERE id = 1; ``` ### Use EXISTS vs IN ```sql -- IN (loads all values into memory) SELECT * FROM users WHERE id IN (SELECT author_id FROM posts WHERE published = true); -- EXISTS (stops at first match) SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM posts p WHERE p.author_id = u.id AND p.published = true); ``` ### Optimize OR Conditions ```sql -- Bad (may not use index) SELECT * FROM users WHERE email = '[email protected]' OR name = 'John'; -- Better (union uses indexes) SELECT * FROM users WHERE email = '[email protected]' UNION SELECT * FROM users WHERE name = 'John'; ``` ### Use Appropriate JOINs ```sql -- INNER JOIN: Only matching rows (most selective) SELECT u.*, p.title FROM users u INNER JOIN posts p ON p.author_id = u.id; -- LEFT JOIN: All from left, matching from right SELECT u.*, p.title FROM users u LEFT JOIN posts p ON p.author_id = u.id; -- Avoid: Cartesian products SELECT * FROM users, posts; -- Bad! ``` ## Caching Strategies ### Query Result Caching ```typescript async function getPopularPosts(): Promise<Post[]> { const cacheKey = 'popular-posts'; const cached = await redis.get(cacheKey); if (cached) { return JSON.parse(cached); } const posts = await prisma.post.findMany({ where: { published: true }, orderBy: { viewCount: 'desc' }, take: 10, }); await redis.setex(cacheKey, 300, JSON.stringify(posts)); // 5 min TTL return posts; } ``` ### Materialized Views ```sql -- Create materialized view for expensive aggregations CREATE MATERIALIZED VIEW post_stats AS SELECT author_id, COUNT(*) as post_count, SUM(view_count) as total_views, MAX(published_at) as last_published FROM posts WHERE published = true GROUP BY author_id; -- Refresh periodically REFRESH MATERIALIZED VIEW post_stats; -- Or refresh concurrently (no lock) REFRESH MATERIALIZED VIEW CONCURRENTLY post_stats; ``` ## Monitoring Queries ### Slow Query Log ```sql -- PostgreSQL: Enable slow query logging ALTER SYSTEM SET log_min_duration_statement = 1000; -- Log queries > 1s -- View slow queries SELECT query, calls, mean_time, total_time FROM pg_stat_statements ORDER BY mean_time DESC LIMIT 10; ``` ### Connection Pooling ```typescript // Prisma with connection pool datasource db { provider = "postgresql" url = env("DATABASE_URL") // Pool settings connectionLimit = 10 } // Or external pooler (PgBouncer) DATABASE_URL="postgres://user:pass@pgbouncer:6432/db?pgbouncer=true" ``` ## Integration Used by: - `database-developer` agent - All database stack skills
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.