Click any command to expand the explanation and examples.
π₯οΈ psql Commands
Connect to a database psql
# Connect psql -U username -d database_name psql -h localhost -p 5432 -U username -d mydbConnection string
psql βpostgresql://user:pass@localhost:5432/mydbβ
Connect to default database
psql -U postgres
\l \dt \d β list databases, tables, columns psql
\l -- List all databases \c mydb -- Connect to database \dt -- List tables in current schema \dt+ -- List tables with sizes \d users -- Describe table (columns, types, indexes) \di -- List indexes \dv -- List views \df -- List functions \dn -- List schemas \du -- List roles/users \dx -- List extensions
psql utility commands psql
\q -- Quit \? -- Help for psql commands \h SELECT -- Help for SQL commands \timing -- Toggle query timing \x -- Toggle expanded display (vertical output) \e -- Open query in editor \i file.sql -- Execute SQL file \o out.txt -- Send output to file \! ls -- Run shell command
π Data Types
Common data types types
-- Numbers INTEGER (INT) -- -2B to 2B BIGINT -- -9Q to 9Q SERIAL -- Auto-increment integer BIGSERIAL -- Auto-increment bigint NUMERIC(10,2) -- Exact decimal (money, etc.) REAL -- 6 decimal digits DOUBLE PRECISION -- 15 decimal digitsUseβ Text VARCHAR(255) β Variable length, max 255 TEXT β Unlimited length CHAR(10) β Fixed length, padded
β Date/Time DATE β 2026-03-14 TIME β 14:30:00 TIMESTAMP β 2026-03-14 14:30:00 TIMESTAMPTZ β With timezone (preferred!) INTERVAL β Time span
β Other BOOLEAN β true/false UUID β gen_random_uuid() JSONB β Binary JSON (preferred over JSON) ARRAY β INTEGER[], TEXT[] BYTEA β Binary data INET β IP address
TIMESTAMPTZ over TIMESTAMP and JSONB over JSON β almost always.
π CRUD Operations
CREATE TABLE ddl
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name TEXT NOT NULL,
role VARCHAR(50) DEFAULT 'user',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
β With foreign key
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
body TEXT,
published BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
INSERT dml
-- Single row
INSERT INTO users (email, name)
VALUES ('alice@example.com', 'Alice');
β Multiple rows
INSERT INTO users (email, name) VALUES
(βbob@example.comβ, βBobβ),
(βcarol@example.comβ, βCarolβ);
β Insert and return the new row
INSERT INTO users (email, name)
VALUES (βdave@example.comβ, βDaveβ)
RETURNING id, email;
β Insert or update (upsert)
INSERT INTO users (email, name)
VALUES (βalice@example.comβ, βAlice Updatedβ)
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;
SELECT β queries dml
-- Basics SELECT * FROM users WHERE role = 'admin'; SELECT name, email FROM users ORDER BY created_at DESC; SELECT * FROM users LIMIT 10 OFFSET 20;β Aggregates SELECT role, COUNT() FROM users GROUP BY role; SELECT role, COUNT() FROM users GROUP BY role HAVING COUNT(*) > 5;
β Joins SELECT u.name, p.title FROM users u JOIN posts p ON p.user_id = u.id WHERE p.published = true;
β Left join (include users with no posts) SELECT u.name, COUNT(p.id) as post_count FROM users u LEFT JOIN posts p ON p.user_id = u.id GROUP BY u.name;
β Subquery SELECT * FROM users WHERE id IN (SELECT user_id FROM posts WHERE published = true);
β CTE (Common Table Expression) WITH active_users AS ( SELECT * FROM users WHERE last_login > NOW() - INTERVAL β30 daysβ ) SELECT * FROM active_users WHERE role = βadminβ;
UPDATE and DELETE dml
-- Update UPDATE users SET name = 'Alice B.' WHERE email = 'alice@example.com'; UPDATE users SET role = 'admin' WHERE id IN (1, 2, 3);β Update with RETURNING UPDATE users SET role = βadminβ WHERE id = 1 RETURNING *;
β Delete DELETE FROM users WHERE id = 5; DELETE FROM users WHERE last_login < NOW() - INTERVAL β1 yearβ;
β Truncate (delete all rows, fast) TRUNCATE TABLE logs;
π Indexes & Performance
CREATE INDEX index
-- B-tree (default, good for =, <, >, BETWEEN) CREATE INDEX idx_users_email ON users(email);β Unique index CREATE UNIQUE INDEX idx_users_email ON users(email);
β Composite index CREATE INDEX idx_posts_user_published ON posts(user_id, published);
β Partial index (only index some rows) CREATE INDEX idx_active_users ON users(email) WHERE active = true;
β GIN index (for JSONB, arrays, full-text) CREATE INDEX idx_users_metadata ON users USING GIN(metadata);
β Drop index DROP INDEX idx_users_email;
EXPLAIN ANALYZE perf
-- See query plan EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';β See actual execution time EXPLAIN ANALYZE SELECT * FROM users WHERE email = βalice@example.comβ;
β Look for: β Seq Scan = full table scan (slow on big tables) β Index Scan = using an index (good) β Nested Loop = might be slow with many rows
π§© JSONB Operations
Query and update JSONB json
-- Access a key SELECT metadata->>'theme' FROM users; -- as text SELECT metadata->'settings' FROM users; -- as JSONβ Nested access SELECT metadata->βsettingsβ->>βlanguageβ FROM users;
β Filter by JSON value SELECT * FROM users WHERE metadata->>βroleβ = βadminβ;
β Check if key exists SELECT * FROM users WHERE metadata ? βthemeβ;
β Contains SELECT * FROM users WHERE metadata @> β{βroleβ: βadminβ}β;
β Update a key UPDATE users SET metadata = metadata || β{βthemeβ: βdarkβ}β WHERE id = 1;
β Remove a key UPDATE users SET metadata = metadata - βthemeβ WHERE id = 1;
β‘ Useful Patterns
Backup and restore admin
# Backup pg_dump -U postgres mydb > backup.sql pg_dump -U postgres -Fc mydb > backup.dump # Custom format (compressed)Restore
psql -U postgres mydb < backup.sql pg_restore -U postgres -d mydb backup.dump
Backup specific table
pg_dump -U postgres -t users mydb > users.sql
ALTER TABLE ddl
-- Add column ALTER TABLE users ADD COLUMN bio TEXT;β Drop column ALTER TABLE users DROP COLUMN bio;
β Rename column ALTER TABLE users RENAME COLUMN name TO full_name;
β Change type ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(500);
β Add constraint ALTER TABLE users ADD CONSTRAINT email_check CHECK (email LIKE β%@%β);
β Rename table ALTER TABLE users RENAME TO accounts;