sql-ops
Quick reference for common SQL patterns, CTEs, window functions, and indexing strategies. Triggers on: sql patterns, cte example, window functions, sql join, index strategy, pagination sql.
Install
npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/sql-ops
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
git clone https://github.com/0xDarkMatter/claude-mods.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
SQL Patterns
Quick reference for common SQL patterns.
CTE (Common Table Expressions)
WITH active_users AS (
SELECT id, name, email
FROM users
WHERE status = 'active'
)
SELECT * FROM active_users WHERE created_at > '2024-01-01';
Chained CTEs
WITH
active_users AS (
SELECT id, name FROM users WHERE status = 'active'
),
user_orders AS (
SELECT user_id, COUNT(*) as order_count
FROM orders GROUP BY user_id
)
SELECT u.name, COALESCE(o.order_count, 0) as orders
FROM active_users u
LEFT JOIN user_orders o ON u.id = o.user_id;
Window Functions (Quick Reference)
| Function | Use |
|---|---|
ROW_NUMBER() |
Unique sequential numbering |
RANK() |
Rank with gaps (1, 2, 2, 4) |
DENSE_RANK() |
Rank without gaps (1, 2, 2, 3) |
LAG(col, n) |
Previous row value |
LEAD(col, n) |
Next row value |
SUM() OVER |
Running total |
AVG() OVER |
Moving average |
SELECT
date,
revenue,
LAG(revenue, 1) OVER (ORDER BY date) as prev_day,
SUM(revenue) OVER (ORDER BY date) as running_total
FROM daily_sales;
JOIN Reference
| Type | Returns |
|---|---|
INNER JOIN |
Only matching rows |
LEFT JOIN |
All left + matching right |
RIGHT JOIN |
All right + matching left |
FULL JOIN |
All rows, NULL where no match |
Pagination
-- OFFSET/LIMIT (simple, slow for large offsets)
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 40;
-- Keyset (fast, scalable)
SELECT * FROM products WHERE id > 42 ORDER BY id LIMIT 20;
Transaction Isolation Levels
| Level | Dirty Read | Non-Repeatable Read | Phantom Read | Use |
|---|---|---|---|---|
READ UNCOMMITTED |
Possible | Possible | Possible | Rarely (PostgreSQL treats as READ COMMITTED) |
READ COMMITTED |
No | Possible | Possible | Default in most databases |
REPEATABLE READ |
No | No | Possible* | Consistent multi-statement reads |
SERIALIZABLE |
No | No | No | Critical invariants (retry on serialization failure) |
*PostgreSQL's REPEATABLE READ also prevents phantoms via snapshot isolation.
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- ... critical operations ...
COMMIT; -- be prepared to retry on serialization failure
Keep the default READ COMMITTED globally; raise the level per-transaction only where the logic requires it.
Index Quick Reference
| Index Type | Best For |
|---|---|
| B-tree | Range queries, ORDER BY |
| Hash | Exact equality only |
| GIN | Arrays, JSONB, full-text |
| Covering | Avoid table lookup |
Anti-Patterns
| Mistake | Fix |
|---|---|
SELECT * |
List columns explicitly |
WHERE YEAR(date) = 2024 |
WHERE date >= '2024-01-01' |
NOT IN with NULLs |
Use NOT EXISTS |
| N+1 queries | Use JOIN or batch |
Additional Resources
For detailed patterns, load:
./references/window-functions.md- Complete window function patterns./references/indexing-strategies.md- Index types, covering indexes, optimization
Files (claude-mods)
-
assets
-
.gitkeep 0 B · in bundle
-
-
references
-
indexing-strategies.md 3.9 KB
# SQL Indexing Strategies Vendor-neutral indexing fundamentals. For PostgreSQL-specific index types (GIN, GiST, BRIN, Hash, partial, expression indexes), see `postgres-ops/references/indexing.md`. ## B-Tree (Default) The standard index type across all major databases. Best for: equality, range queries, ORDER BY, prefix LIKE. ```sql -- Standard index CREATE INDEX idx_users_email ON users(email); -- Unique index CREATE UNIQUE INDEX idx_users_email ON users(email); -- Works well for: WHERE email = 'x@y.com' -- equality WHERE email LIKE 'john%' -- prefix search WHERE created_at > '2024-01-01' -- range ORDER BY created_at -- sorting ``` ## Composite Indexes ### Column Order Matters ```sql -- Leftmost prefix rule CREATE INDEX idx_orders ON orders(user_id, status, created_at); -- This index supports: WHERE user_id = 123 -- yes WHERE user_id = 123 AND status = 'pending' -- yes WHERE user_id = 123 AND status = 'pending' AND created_at > '2024-01-01' -- yes WHERE user_id = 123 AND created_at > '2024-01-01' -- partial (user_id only) WHERE status = 'pending' -- no (user_id not present) ``` ### Optimal Column Order ```sql -- Rule: equality columns first, then range columns -- Most selective equality column first when multiple equalities -- If filtering by status (equality) and date range: CREATE INDEX idx_orders_status_date ON orders(status, created_at); -- If user_id is more selective than status: CREATE INDEX idx_orders_user_status_date ON orders(user_id, status, created_at); ``` ## Covering Indexes Include extra columns to avoid table lookup (index-only scan): ```sql -- Query needs name but filters by email SELECT name FROM users WHERE email = 'x@y.com'; -- Covering index (PostgreSQL INCLUDE, SQL Server INCLUDE) CREATE INDEX idx_users_email_name ON users(email) INCLUDE (name); -- Now the query uses index-only scan (no table access needed) ``` ### When to Use ```sql -- Frequently accessed columns in SELECT CREATE INDEX idx_orders_status ON orders(status) INCLUDE (total, created_at); -- Supports without table access: SELECT total, created_at FROM orders WHERE status = 'pending'; ``` ## Query Analysis with EXPLAIN ```sql -- Basic plan EXPLAIN SELECT * FROM users WHERE email = 'x@y.com'; -- Key scan types to look for: -- Seq Scan - Full table scan (bad for large tables) -- Index Scan - Using index, then fetching rows -- Index Only Scan - Using covering index (best) -- Bitmap Scan - Multiple index conditions combined ``` ```sql -- With actual execution metrics EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'pending'; -- Shows: -- Planning Time: 0.5 ms -- Execution Time: 12.3 ms -- actual rows vs estimated rows (mismatch = stale statistics) ``` ## Anti-Patterns | Mistake | Why | Fix | |---------|-----|-----| | Function on indexed column | Prevents index use | Expression index or rewrite query | | `WHERE col LIKE '%text%'` | Leading wildcard, no B-tree match | Full-text search or trigram index | | `OR` across different columns | May skip index | Rewrite as `UNION ALL` | | Over-indexing | Slows writes, wastes space | Audit unused indexes regularly | | Missing index on FK column | Slow cascading deletes, slow joins | Add B-tree on FK columns | ## Quick Reference | Scenario | Index Strategy | |----------|---------------| | Equality lookup | B-tree on column | | Range queries | B-tree on column | | Multiple conditions | Composite (equality first, range last) | | Avoid table access | Covering index with INCLUDE | | Case-insensitive | Expression index on LOWER() | | Full-text search | Database-specific (GIN in PostgreSQL) | ## See Also - **PostgreSQL-specific**: `postgres-ops/references/indexing.md` - GIN, GiST, BRIN, Hash, partial, expression indexes - **SQLite-specific**: `sqlite-ops` - SQLite indexing considerations -
window-functions.md 6.9 KB
# SQL Window Functions Complete reference for window functions and analytical queries. ## Core Window Functions ### ROW_NUMBER Assigns unique sequential numbers within partition: ```sql -- Rank employees by salary within department SELECT name, department, salary, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank FROM employees; -- Get top N per group WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rn FROM employees ) SELECT * FROM ranked WHERE rn <= 3; ``` ### RANK and DENSE_RANK Handle ties differently: ```sql -- RANK: 1, 2, 2, 4 (skips after ties) -- DENSE_RANK: 1, 2, 2, 3 (no skip) SELECT name, score, RANK() OVER (ORDER BY score DESC) as rank, DENSE_RANK() OVER (ORDER BY score DESC) as dense_rank FROM contestants; -- Result for scores [100, 95, 95, 90]: -- name score rank dense_rank -- Alice 100 1 1 -- Bob 95 2 2 -- Carol 95 2 2 -- Dave 90 4 3 ``` ### NTILE Divide into N equal groups: ```sql -- Divide into quartiles SELECT name, salary, NTILE(4) OVER (ORDER BY salary) as quartile FROM employees; -- Percentile buckets SELECT name, score, NTILE(100) OVER (ORDER BY score) as percentile FROM students; ``` ## Navigation Functions ### LAG and LEAD Access previous/next rows: ```sql -- Previous and next day revenue SELECT date, revenue, LAG(revenue, 1) OVER (ORDER BY date) as prev_day, LEAD(revenue, 1) OVER (ORDER BY date) as next_day, revenue - LAG(revenue, 1) OVER (ORDER BY date) as day_change FROM daily_sales; -- With default value for first/last SELECT date, revenue, LAG(revenue, 1, 0) OVER (ORDER BY date) as prev_or_zero FROM daily_sales; -- Multiple periods back SELECT date, revenue, LAG(revenue, 7) OVER (ORDER BY date) as same_day_last_week FROM daily_sales; ``` ### FIRST_VALUE and LAST_VALUE Get first/last value in window: ```sql -- Compare to first sale of month SELECT date, revenue, FIRST_VALUE(revenue) OVER ( PARTITION BY DATE_TRUNC('month', date) ORDER BY date ) as first_day_revenue, revenue - FIRST_VALUE(revenue) OVER ( PARTITION BY DATE_TRUNC('month', date) ORDER BY date ) as diff_from_first FROM daily_sales; -- Note: LAST_VALUE needs explicit frame SELECT date, revenue, LAST_VALUE(revenue) OVER ( ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) as last_revenue FROM daily_sales; ``` ### NTH_VALUE Get Nth value in window: ```sql -- Get 2nd highest salary per department SELECT department, name, salary, NTH_VALUE(salary, 2) OVER ( PARTITION BY department ORDER BY salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) as second_highest FROM employees; ``` ## Aggregate Window Functions ### Running Totals ```sql -- Running total SELECT date, amount, SUM(amount) OVER (ORDER BY date) as running_total FROM transactions; -- Running total by category SELECT date, category, amount, SUM(amount) OVER ( PARTITION BY category ORDER BY date ) as category_running_total FROM transactions; ``` ### Moving Averages ```sql -- 7-day moving average SELECT date, value, AVG(value) OVER ( ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW ) as moving_avg_7day FROM metrics; -- Centered moving average SELECT date, value, AVG(value) OVER ( ORDER BY date ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING ) as centered_avg FROM metrics; ``` ### Cumulative Statistics ```sql -- Running count, sum, avg, min, max SELECT date, revenue, COUNT(*) OVER (ORDER BY date) as cumulative_count, SUM(revenue) OVER (ORDER BY date) as cumulative_sum, AVG(revenue) OVER (ORDER BY date) as cumulative_avg, MIN(revenue) OVER (ORDER BY date) as cumulative_min, MAX(revenue) OVER (ORDER BY date) as cumulative_max FROM daily_sales; ``` ## Window Frame Specification ### ROWS vs RANGE ```sql -- ROWS: Physical row count SELECT date, revenue, SUM(revenue) OVER ( ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) as sum_3_rows FROM sales; -- RANGE: Logical value range (careful with duplicates) SELECT date, revenue, SUM(revenue) OVER ( ORDER BY date RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW ) as sum_7_days FROM sales; ``` ### Frame Boundaries ```sql -- All frames available ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -- Entire partition ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- From start to here ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING -- From here to end ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING -- 7 rows centered ROWS BETWEEN 6 PRECEDING AND CURRENT ROW -- 7 rows trailing ``` ## Practical Examples ### Year-over-Year Comparison ```sql SELECT date, revenue, LAG(revenue, 365) OVER (ORDER BY date) as revenue_year_ago, revenue - LAG(revenue, 365) OVER (ORDER BY date) as yoy_change, ROUND(100.0 * (revenue - LAG(revenue, 365) OVER (ORDER BY date)) / NULLIF(LAG(revenue, 365) OVER (ORDER BY date), 0), 2) as yoy_pct FROM daily_sales; ``` ### Running Percentage of Total ```sql SELECT category, sales, SUM(sales) OVER () as total, ROUND(100.0 * sales / SUM(sales) OVER (), 2) as pct_of_total, ROUND(100.0 * SUM(sales) OVER (ORDER BY sales DESC) / SUM(sales) OVER (), 2) as cumulative_pct FROM category_sales; ``` ### Session/Gap Detection ```sql -- Find sessions (gaps > 30 minutes = new session) WITH events_with_gaps AS ( SELECT *, EXTRACT(EPOCH FROM (timestamp - LAG(timestamp) OVER ( PARTITION BY user_id ORDER BY timestamp ))) / 60 as minutes_since_last FROM user_events ) SELECT *, SUM(CASE WHEN minutes_since_last > 30 OR minutes_since_last IS NULL THEN 1 ELSE 0 END) OVER ( PARTITION BY user_id ORDER BY timestamp ) as session_id FROM events_with_gaps; ``` ### Deduplication with Row Number ```sql -- Keep only the latest record per user WITH ranked AS ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY updated_at DESC ) as rn FROM users ) SELECT * FROM ranked WHERE rn = 1; ``` ## Performance Tips 1. **Index the ORDER BY column** - Window functions sort data 2. **Limit partitions** - Large partitions = more memory 3. **Named windows** - Reuse window definitions 4. **Avoid nested windows** - Use CTEs instead ### Named Windows ```sql SELECT name, department, salary, ROW_NUMBER() OVER dept_salary as rank, AVG(salary) OVER dept_salary as dept_avg, salary - AVG(salary) OVER dept_salary as diff_from_avg FROM employees WINDOW dept_salary AS (PARTITION BY department ORDER BY salary DESC); ```
-
-
scripts
-
.gitkeep 0 B · in bundle
-
-
SKILL.md 3.4 KB
--- name: sql-ops description: "Quick reference for common SQL patterns, CTEs, window functions, and indexing strategies. Triggers on: sql patterns, cte example, window functions, sql join, index strategy, pagination sql." license: MIT allowed-tools: "Read Write" metadata: author: claude-mods related-skills: postgres-ops, sqlite-ops --- # SQL Patterns Quick reference for common SQL patterns. ## CTE (Common Table Expressions) ```sql WITH active_users AS ( SELECT id, name, email FROM users WHERE status = 'active' ) SELECT * FROM active_users WHERE created_at > '2024-01-01'; ``` ### Chained CTEs ```sql WITH active_users AS ( SELECT id, name FROM users WHERE status = 'active' ), user_orders AS ( SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id ) SELECT u.name, COALESCE(o.order_count, 0) as orders FROM active_users u LEFT JOIN user_orders o ON u.id = o.user_id; ``` ## Window Functions (Quick Reference) | Function | Use | |----------|-----| | `ROW_NUMBER()` | Unique sequential numbering | | `RANK()` | Rank with gaps (1, 2, 2, 4) | | `DENSE_RANK()` | Rank without gaps (1, 2, 2, 3) | | `LAG(col, n)` | Previous row value | | `LEAD(col, n)` | Next row value | | `SUM() OVER` | Running total | | `AVG() OVER` | Moving average | ```sql SELECT date, revenue, LAG(revenue, 1) OVER (ORDER BY date) as prev_day, SUM(revenue) OVER (ORDER BY date) as running_total FROM daily_sales; ``` ## JOIN Reference | Type | Returns | |------|---------| | `INNER JOIN` | Only matching rows | | `LEFT JOIN` | All left + matching right | | `RIGHT JOIN` | All right + matching left | | `FULL JOIN` | All rows, NULL where no match | ## Pagination ```sql -- OFFSET/LIMIT (simple, slow for large offsets) SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 40; -- Keyset (fast, scalable) SELECT * FROM products WHERE id > 42 ORDER BY id LIMIT 20; ``` ## Transaction Isolation Levels | Level | Dirty Read | Non-Repeatable Read | Phantom Read | Use | |-------|-----------|--------------------|--------------|-----| | `READ UNCOMMITTED` | Possible | Possible | Possible | Rarely (PostgreSQL treats as READ COMMITTED) | | `READ COMMITTED` | No | Possible | Possible | Default in most databases | | `REPEATABLE READ` | No | No | Possible* | Consistent multi-statement reads | | `SERIALIZABLE` | No | No | No | Critical invariants (retry on serialization failure) | *PostgreSQL's REPEATABLE READ also prevents phantoms via snapshot isolation. ```sql BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- ... critical operations ... COMMIT; -- be prepared to retry on serialization failure ``` Keep the default `READ COMMITTED` globally; raise the level per-transaction only where the logic requires it. ## Index Quick Reference | Index Type | Best For | |------------|----------| | B-tree | Range queries, ORDER BY | | Hash | Exact equality only | | GIN | Arrays, JSONB, full-text | | Covering | Avoid table lookup | ## Anti-Patterns | Mistake | Fix | |---------|-----| | `SELECT *` | List columns explicitly | | `WHERE YEAR(date) = 2024` | `WHERE date >= '2024-01-01'` | | `NOT IN` with NULLs | Use `NOT EXISTS` | | N+1 queries | Use JOIN or batch | ## Additional Resources For detailed patterns, load: - `./references/window-functions.md` - Complete window function patterns - `./references/indexing-strategies.md` - Index types, covering indexes, optimization
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.