πŸ“‹ Cheat Sheets

PostgreSQL Cheat Sheet β€” Queries, psql Commands, and Data Types


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 mydb

Connection 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 digits

β€” 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

Use 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;