PostgreSQL Advanced

CTEs, JSONB, index types, query planning, and partitioning

CTEs & JSONB

sql
-- CTE (Common Table Expression)
WITH active_orders AS (
  SELECT user_id, COUNT(*) AS cnt FROM orders
  WHERE status = 'active' GROUP BY user_id
)
SELECT u.name, ao.cnt
FROM users u JOIN active_orders ao ON u.id = ao.user_id
WHERE ao.cnt > 3;

-- Recursive CTE (org chart / tree)
WITH RECURSIVE subordinates AS (
  SELECT id, name, manager_id FROM employees WHERE name = 'CEO'
  UNION ALL
  SELECT e.id, e.name, e.manager_id
  FROM employees e JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates;

-- JSONB — store and query JSON
CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT,
  attributes JSONB DEFAULT '{}'
);

INSERT INTO products (name, attributes)
VALUES ('Laptop', '{"color": "silver", "ram": 16, "tags": ["sale"]}');

-- Query JSONB
SELECT * FROM products WHERE attributes @> '{"color": "silver"}';
SELECT * FROM products WHERE attributes->>'ram' = '16';
SELECT * FROM products WHERE attributes->'tags' ? 'sale';

Index Types & Query Planning

sql
-- B-Tree (default) — equality and range queries
CREATE INDEX idx_email ON users (email);

-- GIN — for JSONB, arrays, full-text search
CREATE INDEX idx_attrs ON products USING GIN (attributes);

-- GiST — geometric and range types
CREATE INDEX idx_location ON places USING GIST (coordinates);

-- Expression index
CREATE INDEX idx_lower_email ON users (LOWER(email));

-- EXPLAIN ANALYZE — check query plan
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@ex.com';
-- Look for: Index Scan (good) vs Seq Scan (bad)

-- Partitioning — split large tables for performance
CREATE TABLE orders (
  id SERIAL, user_id INT, created_at TIMESTAMPTZ, total DECIMAL
) PARTITION BY RANGE (created_at);

CREATE TABLE orders_2025 PARTITION OF orders
  FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');

Performance Tips

  • Add indexes on JOIN columns and WHERE conditions
  • Use EXPLAIN ANALYZE to verify index usage
  • Avoid SELECT * — select only needed columns
  • Use connection pooling (PgBouncer / built-in)
  • VACUUM and ANALYZE regularly
  • Partition large tables (>10M rows) by date or ID range

💬 When to use GIN vs GiST vs B-Tree index?

B-Tree (default): equality, range, sorting — most common. GIN: contains/overlap queries — JSONB, arrays, full-text. GiST: proximity queries — geometry, ranges, nearest-neighbor. Use B-Tree unless you have a specific need for GIN/GiST.