Install
$ agentstack add skill-jonathan0823-opencode-config-postgresql-patterns ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
PostgreSQL Patterns Skill
Overview
This skill provides guidelines for designing, querying, and optimizing PostgreSQL databases for production applications.
Schema Design
1. Table Design
-- DO: Use appropriate data types
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(100),
age INTEGER CHECK (age >= 0 AND age = 0),
status VARCHAR(20) DEFAULT 'pending'
CHECK (status IN ('pending', 'processing', 'shipped', 'delivered', 'cancelled')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Many-to-Many with junction table
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL CHECK (price >= 0)
);
CREATE TABLE order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
quantity INTEGER NOT NULL CHECK (quantity > 0),
price_at_time DECIMAL(10, 2) NOT NULL,
UNIQUE(order_id, product_id) -- Prevent duplicates
);
-- Self-referential (hierarchical)
CREATE TABLE categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
parent_id UUID REFERENCES categories(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
3. Indexes
-- DO: Index frequently queried columns
CREATE INDEX idx_users_email ON users(email);
-- DO: Composite indexes for multi-column queries
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
-- DO: Partial indexes for filtered queries
CREATE INDEX idx_orders_pending ON orders(created_at)
WHERE status = 'pending';
-- DO: Expression indexes
CREATE INDEX idx_users_email_lower ON users(LOWER(email));
-- DO: GIN index for JSONB/array
CREATE INDEX idx_users_metadata ON users USING GIN(metadata);
-- DO: Index for text search
CREATE INDEX idx_products_name_trgm ON products
USING gin(name gin_trgm_ops);
-- DO: BRIN index for large, naturally ordered tables
CREATE INDEX idx_events_created_brin ON events
USING BRIN(created_at) WITH (pages_per_range = 128);
-- DON'T: Over-index
-- Every index slows down writes
-- Monitor with: SELECT * FROM pg_stat_user_indexes WHERE idx_scan = 0;
Query Patterns
1. Select Queries
-- DO: Select only needed columns
SELECT id, email, full_name FROM users WHERE id = 'uuid';
-- DON'T: SELECT * in production
SELECT * FROM users; -- Avoid this
-- DO: Use LIMIT with large datasets
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20;
-- DO: Use OFFSET for pagination (consider keyset for large datasets)
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20 OFFSET 40;
-- DO: Use keyset pagination for better performance
SELECT * FROM orders
WHERE created_at 0
ORDER BY total_spent DESC;
-- DO: Use CTEs for complex queries
WITH user_stats AS (
SELECT
user_id,
COUNT(*) as order_count,
SUM(total_amount) as total_revenue
FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id
)
SELECT
u.email,
us.order_count,
us.total_revenue
FROM users u
JOIN user_stats us ON u.id = us.user_id
WHERE us.total_revenue > 1000;
3. Insert/Update/Delete
-- DO: Use INSERT with ON CONFLICT (upsert)
INSERT INTO users (email, username, password_hash)
VALUES ('test@example.com', 'testuser', 'hash')
ON CONFLICT (email) DO UPDATE SET
password_hash = EXCLUDED.password_hash,
updated_at = CURRENT_TIMESTAMP
RETURNING id;
-- DO: Batch inserts
INSERT INTO order_items (order_id, product_id, quantity, price_at_time)
VALUES
('order-1', 'product-1', 2, 29.99),
('order-1', 'product-2', 1, 49.99),
('order-1', 'product-3', 3, 9.99);
-- DO: Use UPDATE with RETURNING
UPDATE users
SET last_login = CURRENT_TIMESTAMP
WHERE id = 'user-id'
RETURNING *;
-- DO: Use DELETE with LIMIT and RETURNING
DELETE FROM temp_logs
WHERE created_at >'user_id'));
-- Query JSONB
SELECT * FROM events
WHERE payload @> '{"event": "login"}';
-- Extract values
SELECT
event_type,
payload->>'user_id' as user_id,
payload->'details'->>'ip' as ip_address
FROM events
WHERE created_at > CURRENT_DATE - INTERVAL '1 day';
-- Update JSONB
UPDATE events
SET payload = payload || '{"processed": true}'::jsonb
WHERE id = 'event-id';
3. Window Functions
-- Running totals
SELECT
user_id,
order_date,
amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY order_date
) as running_total
FROM orders;
-- Ranking
SELECT
product_id,
sales_amount,
RANK() OVER (ORDER BY sales_amount DESC) as rank,
DENSE_RANK() OVER (ORDER BY sales_amount DESC) as dense_rank
FROM product_sales;
-- Lag/Lead for comparisons
SELECT
user_id,
login_date,
LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date) as prev_login,
login_date - LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date) as days_between
FROM user_logins;
Optimization
1. Query Analysis
-- Analyze query execution
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
-- Check for seq scans
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 'uuid';
-- Look for high-cost operations
-- - Seq Scan on large tables (should be index scan)
-- - Nested Loop with high row counts (consider hash join)
-- - Sort operations (consider index for ordering)
2. Performance Tips
-- DO: Use prepared statements (automatic in most ORMs)
PREPARE get_user (UUID) AS
SELECT * FROM users WHERE id = $1;
EXECUTE get_user('uuid');
-- DO: Vacuum and analyze regularly
VACUUM ANALYZE users;
-- DO: Partition large tables
CREATE TABLE events_2024 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
-- DO: Use connection pooling (PgBouncer)
-- DO: Set appropriate work_mem for complex queries
SET work_mem = '256MB';
3. Monitoring Queries
-- Find slow queries
SELECT
query,
calls,
total_time,
mean_time,
rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;
-- Check index usage
SELECT
schemaname,
tablename,
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
Migration Patterns
1. Zero-Downtime Migrations
-- Phase 1: Add column (nullable)
ALTER TABLE users ADD COLUMN full_name VARCHAR(200);
-- Phase 2: Dual-write in application code
-- Write to both first_name/last_name AND full_name
-- Phase 3: Backfill data
UPDATE users
SET full_name = CONCAT(first_name, ' ', last_name)
WHERE full_name IS NULL;
-- Phase 4: Make NOT NULL after backfill
ALTER TABLE users ALTER COLUMN full_name SET NOT NULL;
-- Phase 5: Update application to read from full_name only
-- Phase 6: Remove old columns
ALTER TABLE users DROP COLUMN first_name;
ALTER TABLE users DROP COLUMN last_name;
2. Transaction Safety
-- DO: Use transactions for related changes
BEGIN;
INSERT INTO orders (user_id, total_amount)
VALUES ('user-uuid', 99.99)
RETURNING id;
INSERT INTO order_items (order_id, product_id, quantity)
VALUES ('order-uuid', 'product-uuid', 1);
UPDATE inventory
SET quantity = quantity - 1
WHERE product_id = 'product-uuid';
COMMIT;
-- DO: Use savepoints for partial rollback
BEGIN;
SAVEPOINT before_payment;
-- Try payment processing
-- If fails:
ROLLBACK TO SAVEPOINT before_payment;
-- Continue with other operations
COMMIT;
When to Use
Use this skill when:
- Designing PostgreSQL schemas
- Writing complex SQL queries
- Optimizing query performance
- Implementing migrations
- Working with JSONB data
- Setting up full-text search
- Analyzing query performance
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Jonathan0823
- Source: Jonathan0823/opencode-config
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.