agentsclimarketplace

Postgres advanced

Skill medy-gribkov/arcana/skills/postgres-advanced

Universal AI development toolkit. 74 production-ready skills for every coding agent. Works with Claude Code, Cursor, Codex.

Install
npx -y skills add medy-gribkov/arcana --skill postgres-advanced

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Advanced PostgreSQL patterns including window functions, CTEs, JSONB operations, full-text search, partitioning, and performance optimization with EXPLAIN ANALYZE

SKILL.md

9.5 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it

Advanced PostgreSQL Operations

Expert-level PostgreSQL patterns for production systems: analytical queries, JSON handling, search optimization, and performance tuning.

Window Functions

Perform calculations across row sets without collapsing results.

BAD: Self-join for ranking

-- Inefficient, creates cartesian product
SELECT e1.name, e1.salary, COUNT(e2.salary) as rank
FROM employees e1
LEFT JOIN employees e2 ON e1.salary <= e2.salary
GROUP BY e1.name, e1.salary;

GOOD: ROW_NUMBER and RANK

-- Efficient window function
SELECT
  name,
  salary,
  department,
  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as row_num,
  RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank,
  DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dense_rank
FROM employees;

BAD: Subquery for previous value

-- Poor performance, scalar subquery per row
SELECT
  date,
  revenue,
  (SELECT revenue FROM sales s2
   WHERE s2.date < s1.date
   ORDER BY date DESC LIMIT 1) as prev_revenue
FROM sales s1;

GOOD: LAG/LEAD for row comparison

-- Single scan with window function
SELECT
  date,
  revenue,
  LAG(revenue, 1) OVER (ORDER BY date) as prev_revenue,
  LEAD(revenue, 1) OVER (ORDER BY date) as next_revenue,
  revenue - LAG(revenue, 1) OVER (ORDER BY date) as revenue_change
FROM sales;

Running totals and moving averages

-- Cumulative sum and 7-day moving average
SELECT
  date,
  amount,
  SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as running_total,
  AVG(amount) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as moving_avg_7d,
  FIRST_VALUE(amount) OVER (ORDER BY date) as first_amount,
  LAST_VALUE(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as last_amount
FROM transactions;

Common Table Expressions (CTEs)

BAD: Nested subqueries

-- Unreadable, hard to maintain
SELECT * FROM (
  SELECT * FROM (
    SELECT user_id, COUNT(*) as cnt
    FROM orders
    GROUP BY user_id
  ) t1 WHERE cnt > 5
) t2 JOIN users ON t2.user_id = users.id;

GOOD: Named CTEs

-- Clear, reusable, optimized by planner
WITH high_value_users AS (
  SELECT user_id, COUNT(*) as order_count
  FROM orders
  WHERE total > 100
  GROUP BY user_id
  HAVING COUNT(*) > 5
),
user_details AS (
  SELECT u.id, u.name, u.email, hvu.order_count
  FROM users u
  JOIN high_value_users hvu ON u.id = hvu.user_id
)
SELECT * FROM user_details WHERE order_count > 10;

BAD: Unbounded recursive CTE

-- Risk of infinite loop
WITH RECURSIVE tree AS (
  SELECT id, parent_id, name FROM categories WHERE id = 1
  UNION ALL
  SELECT c.id, c.parent_id, c.name
  FROM categories c JOIN tree t ON c.parent_id = t.id
)
SELECT * FROM tree;

GOOD: Recursive CTE with depth limit

-- Safe recursion with cycle detection
WITH RECURSIVE category_tree AS (
  SELECT id, parent_id, name, 1 as depth, ARRAY[id] as path
  FROM categories WHERE parent_id IS NULL
  UNION ALL
  SELECT c.id, c.parent_id, c.name, ct.depth + 1, ct.path || c.id
  FROM categories c
  JOIN category_tree ct ON c.parent_id = ct.id
  WHERE ct.depth < 10 AND NOT c.id = ANY(ct.path)
)
SELECT * FROM category_tree ORDER BY path;

JSONB Operations

BAD: No index on JSON queries

-- Table scan on every query
SELECT * FROM products
WHERE metadata->>'brand' = 'Acme';

GOOD: GIN index on JSONB

-- Create GIN index for fast JSON queries
CREATE INDEX idx_products_metadata ON products USING GIN (metadata);

-- Containment operator (uses index)
SELECT * FROM products
WHERE metadata @> '{"brand": "Acme"}';

-- Path queries with index
SELECT * FROM products
WHERE metadata->>'brand' = 'Acme'
  AND metadata->'specs'->>'color' = 'red';

JSONB manipulation and aggregation

-- Update nested JSON
UPDATE products
SET metadata = jsonb_set(
  metadata,
  '{specs,color}',
  '"blue"'
)
WHERE id = 123;

-- Aggregate into JSONB
SELECT
  category,
  jsonb_agg(
    jsonb_build_object(
      'id', id,
      'name', name,
      'price', price
    )
  ) as products
FROM products
GROUP BY category;

-- Extract and transform
SELECT
  id,
  metadata->>'brand' as brand,
  (metadata->'specs'->'dimensions'->>'height')::numeric as height,
  jsonb_array_elements_text(metadata->'tags') as tag
FROM products;

Full-Text Search

BAD: LIKE pattern matching

-- Cannot use indexes efficiently
SELECT * FROM articles
WHERE content LIKE '%postgresql%'
  OR title LIKE '%postgresql%';

GOOD: tsvector with GIN index

-- Create tsvector column
ALTER TABLE articles
ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
  setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
  setweight(to_tsvector('english', coalesce(content, '')), 'B')
) STORED;

-- Create GIN index
CREATE INDEX idx_articles_search ON articles USING GIN (search_vector);

-- Fast full-text search with ranking
SELECT
  id,
  title,
  ts_rank(search_vector, query) as rank
FROM articles,
  websearch_to_tsquery('english', 'postgresql performance') as query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;

-- Highlight search terms
SELECT
  ts_headline(
    'english',
    content,
    websearch_to_tsquery('english', 'postgresql performance'),
    'MaxWords=50, MinWords=25'
  ) as snippet
FROM articles
WHERE search_vector @@ websearch_to_tsquery('english', 'postgresql performance');

Performance Optimization

BAD: SELECT * in production

-- Fetches unnecessary data, prevents covering indexes
SELECT * FROM orders WHERE status = 'pending';

GOOD: Specific columns with covering index

-- Covering index (index-only scan)
CREATE INDEX idx_orders_status_covering
ON orders (status)
INCLUDE (id, created_at, total);

SELECT id, created_at, total
FROM orders
WHERE status = 'pending';

Partial indexes for common queries

-- Index only active records
CREATE INDEX idx_users_active_email
ON users (email)
WHERE deleted_at IS NULL;

-- Index only recent orders
CREATE INDEX idx_orders_recent
ON orders (created_at DESC)
WHERE created_at > NOW() - INTERVAL '90 days';

EXPLAIN ANALYZE interpretation

-- Get actual execution plan
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5;

-- Look for:
-- 1. Sequential Scans on large tables → add indexes
-- 2. High "Rows Removed by Filter" → tighten WHERE clause
-- 3. Nested Loop on large datasets → check join conditions
-- 4. High "Buffers: shared hit" vs "read" → cache hit ratio

Table Partitioning

-- Range partitioning by date
CREATE TABLE events (
  id BIGSERIAL,
  event_type VARCHAR(50),
  data JSONB,
  created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);

-- Create partitions
CREATE TABLE events_2024_q1
PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');

CREATE TABLE events_2024_q2
PARTITION OF events
FOR VALUES FROM ('2024-04-01') TO ('2024-07-01');

-- Create default partition
CREATE TABLE events_default
PARTITION OF events DEFAULT;

-- Indexes on partitions
CREATE INDEX idx_events_2024_q1_type
ON events_2024_q1 (event_type);

-- Partition pruning (automatic with PG 11+)
EXPLAIN SELECT * FROM events
WHERE created_at BETWEEN '2024-02-01' AND '2024-02-28';
-- Only scans events_2024_q1 partition

Advanced Features

Advisory locks for coordination

-- Prevent concurrent execution of job
SELECT pg_advisory_lock(12345);

-- Process data...
UPDATE jobs SET status = 'processing' WHERE id = 100;

-- Release lock
SELECT pg_advisory_unlock(12345);

-- Try lock (non-blocking)
DO $$
BEGIN
  IF pg_try_advisory_lock(12345) THEN
    -- Got lock, do work
    RAISE NOTICE 'Processing job';
    PERFORM pg_advisory_unlock(12345);
  ELSE
    RAISE NOTICE 'Job already running';
  END IF;
END $$;

Connection pooling with PgBouncer

# pgbouncer.ini
[databases]
mydb = host=localhost dbname=mydb

[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
reserve_pool_size = 5
reserve_pool_timeout = 3

Logical replication for zero-downtime migrations

-- On source database
CREATE PUBLICATION my_publication
FOR TABLE users, orders;

-- On target database
CREATE SUBSCRIPTION my_subscription
CONNECTION 'host=source-db port=5432 dbname=mydb'
PUBLICATION my_publication;

-- Monitor replication lag
SELECT
  slot_name,
  active,
  pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) as lag
FROM pg_replication_slots;

Workflow

  1. Design queries: Start with CTEs for readability, use window functions for analytics
  2. Add indexes: GIN for JSONB/tsvector, partial indexes for filtered queries, covering indexes for hot paths
  3. Analyze performance: Run EXPLAIN ANALYZE, check for sequential scans and high buffer reads
  4. Optimize schema: Consider partitioning for large time-series tables, use JSONB for flexible schemas
  5. Monitor production: Track query execution time, connection pool utilization, replication lag

Use window functions for row-based calculations, CTEs for complex queries, JSONB indexes for JSON searches, and tsvector for full-text search. Always EXPLAIN ANALYZE before deploying performance-critical queries.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.