← Back to PostgreSQL Menu | 🏠 Hub
-- Create index
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_created ON posts(created_at DESC);
-- Unique index
CREATE UNIQUE INDEX idx_users_email_uniq ON users(email);
-- Composite index (multiple columns)
CREATE INDEX idx_posts_user_date ON posts(user_id, created_at DESC);
-- Good for: WHERE user_id = ? ORDER BY created_at DESC
-- Partial index (subset of rows)
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
-- Only indexes active users — smaller, faster!
-- GIN index for JSONB and ARRAY
CREATE INDEX idx_products_meta ON products USING GIN(metadata);
CREATE INDEX idx_articles_tags ON articles USING GIN(tags);
-- Enables fast: WHERE metadata @> '{"brand":"Dell"}'
-- Full-text search index
CREATE INDEX idx_posts_search ON posts USING GIN(to_tsvector('english', title || ' ' || body));
-- List indexes
SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'users';
-- Drop index
DROP INDEX idx_users_email;
-- See query execution plan EXPLAIN SELECT * FROM users WHERE email = 'ko@test.com'; -- "Index Scan using idx_users_email on users (cost=0.28..8.30 rows=1)" -- Actual runtime stats EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'ko@test.com'; -- Execution Time: 0.045 ms -- WITHOUT index: "Seq Scan" (full table scan) — slow! -- WITH index: "Index Scan" — fast! -- Expensive operations to watch for: -- Seq Scan → add index on that column -- Hash Join → usually OK for small tables -- Nested Loop → might need index -- Sort → add index on ORDER BY column -- Enable timing \timing on
-- ACID: Atomicity, Consistency, Isolation, Durability
-- Transaction = multiple operations ကို all-or-nothing group
-- Bank transfer example
BEGIN;
-- Deduct from sender
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
-- Verify balance OK
DO $$ BEGIN
IF (SELECT balance FROM accounts WHERE id = 1) < 0 THEN
RAISE EXCEPTION 'Insufficient funds!';
END IF;
END; $$;
-- Add to receiver
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT; -- ← all success → save permanently
-- If anything fails: ROLLBACK
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
-- Error occurs here...
ROLLBACK; -- ← undo everything — both accounts unchanged!
-- SAVEPOINT — partial rollback
BEGIN;
INSERT INTO orders (user_id, total) VALUES (1, 5000);
SAVEPOINT order_created;
INSERT INTO order_items (order_id, product_id, qty) VALUES (1, 5, 2);
-- Error: product 5 out of stock
ROLLBACK TO order_created; -- ← undo items but keep order
INSERT INTO order_items (order_id, product_id, qty) VALUES (1, 3, 2);
COMMIT;
-- VIEW — saved query (runs every time)
CREATE VIEW active_user_summary AS
SELECT
u.id,
u.name,
u.email,
COUNT(p.id) AS post_count,
MAX(p.created_at) AS last_post
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
WHERE u.active = true
GROUP BY u.id, u.name, u.email;
-- Use like a table
SELECT * FROM active_user_summary WHERE post_count > 10;
-- MATERIALIZED VIEW — cached result (fast read, manual refresh)
CREATE MATERIALIZED VIEW monthly_revenue AS
SELECT
DATE_TRUNC('month', created_at) AS month,
SUM(total) AS revenue,
COUNT(*) AS orders
FROM orders
GROUP BY month;
-- Fast read (cached)
SELECT * FROM monthly_revenue;
-- Refresh cache
REFRESH MATERIALIZED VIEW monthly_revenue;
-- Schedule: pg_cron extension or application scheduler
← PostgreSQL 04 | Next: PostgreSQL 06 → Node.js + pg →