PostgreSQL — Overview
What Is PostgreSQL
PostgreSQL is an open-source object-relational database focused on reliability and flexibility. In practice, it lets you use classic SQL tables and modern data styles (JSON, full-text search, geospatial, vectors) in one system.
Why PostgreSQL
| Strength |
Detail |
| ACID compliance |
Transactions are safe: COMMIT saves, ROLLBACK cancels |
| SQL standard |
Strong SQL support for joins, analytics, and constraints |
| Extensibility |
Extensions add features: pgvector, PostGIS, pg_cron |
| JSON support |
jsonb works well with indexes for document-like data |
| Concurrency |
Many reads/writes at once without frequent lock pain |
| Ecosystem |
Mature tools, cloud-managed options (RDS, Cloud SQL, Supabase, Neon) |
| Open source |
No vendor lock-in, permissive license |
How It Works in Daily Use
App / psql sends SQL
│
├── PostgreSQL validates query and permissions
├── Builds execution plan
├── Reads/writes data
└── Returns rows or status
This guide stays at the practical SQL/operations level and intentionally skips internal process architecture details.
PostgreSQL vs Alternatives
| Aspect |
PostgreSQL |
MySQL |
SQLite |
MongoDB |
| Type |
Object-relational |
Relational |
Embedded relational |
Document |
| ACID |
Full |
Full (InnoDB) |
Full |
Per-document |
| JSON support |
jsonb (indexed) |
JSON (limited) |
JSON1 extension |
Native (BSON) |
| Extensions |
Rich (pgvector, PostGIS) |
Limited |
Minimal |
Plugins |
| Scaling |
Vertical + read replicas |
Vertical + read replicas |
Single file |
Horizontal (sharding) |
| Best for |
General purpose, analytics |
Web apps, read-heavy |
Embedded, mobile, tests |
Flexible schema, prototyping |
When to Choose PostgreSQL
| Good Fit |
Consider Alternative |
| Complex queries with joins |
Simple key-value lookups → Redis |
| ACID transactions required |
Schemaless rapid prototyping → MongoDB |
| Mix of SQL + JSON data |
Embedded / mobile / serverless → SQLite |
Full-text search (built-in tsvector) |
Heavy search workload → Elasticsearch |
| Geospatial (PostGIS) |
Graph traversal → Neo4j |
| Vector similarity (pgvector) |
Dedicated vector infra at scale → Pinecone |
Key Extensions
| Extension |
Purpose |
pgvector |
Vector similarity search (AI/LLM) |
PostGIS |
Geospatial data and queries |
pg_stat_statements |
Query performance statistics |
pg_cron |
Scheduled jobs inside PostgreSQL |
pgcrypto |
Encryption functions |
uuid-ossp / gen_random_uuid() |
UUID generation |
hstore |
Key-value pairs in a column |
Section Map
| File |
Topics |
| 01 — Commands & psql |
Connection, navigation, psql shortcuts, import/export |
| 02 — Schema & Data Types |
Tables, columns, types, constraints, indexes |
| 03 — Queries & Performance |
EXPLAIN ANALYZE, indexing strategy, pagination, window function tips, anti-patterns |
| 04 — Admin & Operations |
Users, roles, backup, Docker setup, tuning |
| 05 — Basic Query Commands |
SQL cheat sheet: CRUD, filters, joins, CTEs, window functions, set ops, transactions |
Cheat Sheet
psql & Connection (01)
| Task |
Command |
| Connect |
psql -U user -d db |
| Connection string |
psql "postgresql://user:pass@host:5432/db" |
| List databases |
\l |
| List tables |
\dt / \dt+ (with sizes) |
| Describe table |
\d tablename / \d+ (detailed) |
| List indexes |
\di |
| Show timing |
\timing |
| Export CSV |
\copy (SELECT ...) TO 'file.csv' WITH CSV HEADER |
| Import CSV |
\copy table FROM 'file.csv' WITH CSV HEADER |
| Run SQL file |
psql -f file.sql |
| Quit |
\q |
Schema & DDL (02)
| Task |
SQL |
| Create table |
CREATE TABLE t (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, ...); |
| Add column |
ALTER TABLE t ADD COLUMN col TEXT; |
| Drop column |
ALTER TABLE t DROP COLUMN col; |
| Rename column |
ALTER TABLE t RENAME COLUMN old TO new; |
| Add constraint |
ALTER TABLE t ADD CONSTRAINT chk CHECK (col > 0); |
| Foreign key |
col BIGINT REFERENCES other(id) ON DELETE CASCADE |
| Create index |
CREATE INDEX idx ON t (col); |
| JSONB index |
CREATE INDEX idx ON t USING gin (jsonb_col); |
| Partial index |
CREATE INDEX idx ON t (col) WHERE active = true; |
| Create view |
CREATE VIEW v AS SELECT ...; |
| Materialized view |
CREATE MATERIALIZED VIEW v AS SELECT ...; REFRESH MATERIALIZED VIEW v; |
Queries (05)
| Task |
SQL |
| Filter |
WHERE col IN (...) / BETWEEN / LIKE / ILIKE / IS NULL |
| Upsert |
INSERT ... ON CONFLICT (col) DO UPDATE SET ... |
| Bulk insert |
INSERT INTO t SELECT ... FROM ... |
| Update from join |
UPDATE t SET col = s.val FROM source s WHERE s.id = t.id |
| Delete with join |
DELETE FROM t USING other o WHERE t.fk = o.id |
| Union (dedup) |
SELECT ... UNION SELECT ... |
| Union (keep all) |
SELECT ... UNION ALL SELECT ... |
| Window rank |
row_number() OVER (PARTITION BY col ORDER BY col) |
| Previous row |
lag(col) OVER (ORDER BY col) |
| Conditional |
CASE WHEN ... THEN ... ELSE ... END |
| Safe NULL |
coalesce(col, default) |
| Cast |
col::type or CAST(col AS type) |
| Truncate |
TRUNCATE table RESTART IDENTITY CASCADE |
| Task |
SQL |
| Explain query |
EXPLAIN (ANALYZE, BUFFERS) SELECT ... |
| Filter JSONB |
WHERE data @> '{"key": "val"}' |
| Array overlap |
WHERE tags && ARRAY['a', 'b'] |
| Full-text search |
WHERE to_tsvector('english', body) @@ to_tsquery('term') |
| Cursor pagination |
WHERE id > last_id ORDER BY id LIMIT n |
| Estimate row count |
SELECT n_live_tup FROM pg_stat_user_tables WHERE relname = 't' |
Admin & Ops (04)
| Task |
Command |
| Create user |
CREATE ROLE name WITH LOGIN PASSWORD '...'; |
| Grant read-only |
GRANT SELECT ON ALL TABLES IN SCHEMA public TO role; |
| Backup DB |
pg_dump -U user -d db -Fc -f backup.dump |
| Restore DB |
pg_restore -U user -d db --clean --if-exists backup.dump |
| Docker connect |
docker compose exec db psql -U user -d db |
| Update stats |
ANALYZE; / ANALYZE tablename; |
| Vacuum |
VACUUM ANALYZE; |
| Active queries |
SELECT pid, now()-query_start AS dur, query FROM pg_stat_activity WHERE state = 'active'; |
| Kill query |
SELECT pg_cancel_backend(pid); |
| Table size |
SELECT pg_size_pretty(pg_total_relation_size('table')); |
| DB size |
SELECT pg_size_pretty(pg_database_size('db')); |
See also