πŸ“‹ Cheat Sheets
Β· 6 min read

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


Some links in this article are affiliate links. We earn a commission at no extra cost to you when you purchase through them. Full disclosure.

Click any command to expand the explanation and examples. For standard SQL syntax, see the SQL cheat sheet. Choosing a database? Read Postgres vs SQLite vs MySQL.

πŸ–₯️ 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;

Quick access: Raycast lets you search commands, snippets, and cheat sheets instantly from your keyboard. Free for Mac.

Related: PostgreSQL vs MySQL Β· PostgreSQL vs SQLite Β· Code Review Sql Query Β· Mongodb Was A Mistake