PostgreSQL Cheatsheet

psql Meta-Commands

Use this PostgreSQL reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Connecting

psql takes either flag-style arguments or a single connection URI. The URI form is what you paste from a hosted provider.

psql -h db.example.com -p 5432 -U app -d appdb
psql "postgres://app:secret@db.example.com:5432/appdb?sslmode=require"
psql "postgresql:///appdb?host=/var/run/postgresql"   # unix socket
SourceWhat it setsNotes
PGHOST PGPORT PGUSER PGDATABASEConnection defaultsBeat compiled-in defaults, lose to flags
PGPASSWORDPasswordConvenient, visible in ps and shell history
~/.pgpassPassword per hosthost:port:db:user:password, must be chmod 0600
PGSERVICEFILE + PGSERVICENamed connection setspg_service.conf with [prod] blocks, then psql service=prod
PGSSLMODE / ?sslmode=TLS policyrequire encrypts but skips verification, verify-full is the real one
PGOPTIONSServer settingsPGOPTIONS='-c statement_timeout=5s'

~/.pgpass line, and the wildcard form:

db.example.com:5432:appdb:app:secret
*:*:*:postgres:localdevpassword

Help and discovery

\? lists meta-commands, \h lists SQL statements. \h <statement> is the fastest path to real syntax without leaving the shell.

CommandShows
\?Every psql meta-command
\? variablesThe special psql variables (ON_ERROR_STOP, AUTOCOMMIT, ...)
\hEvery SQL statement psql knows
\h CREATE INDEXFull grammar for one statement
\copyrightLicense text
\conninfoWho and where you are connected as

Inspecting objects

The \d family is the schema browser. Add + for sizes, storage, and descriptions, add S to include system objects, and pass a pattern to filter.

CommandLists
\l / \l+Databases, with size and tablespace
\c dbnameConnect to another database (same session, new backend)
\c dbname userReconnect as a different role
\dtTables in search_path
\dt public.*Tables in one schema
\d ordersColumns, indexes, constraints, triggers of one table
\d+ ordersSame plus storage, stats target, size, comments
\dn / \dn+Schemas, with privileges
\dfFunctions
\df+ my_fnFunction source and volatility
\dvViews
\dmMaterialized views
\diIndexes
\dsSequences
\du / \dgRoles and their attributes
\dp / \zTable, view, and sequence privileges (ACLs)
\dxInstalled extensions
\dT+Types, including enum labels
\dRp / \dRsLogical replication publications and subscriptions

Patterns accept * and ?, so \dt user* matches users and user_tokens.

Output, timing, and files

Wide rows are unreadable in aligned mode. \x auto switches to record-per-line formatting only when a row does not fit the terminal.

CommandEffect
\x autoExpanded output when rows are too wide
\timing onPrint wall-clock duration after every statement
\pset null '(null)'Make NULL visible instead of blank
\pset pager offStop piping into less
\o out.txtRedirect all results to a file, \o alone stops
\o | gzip > out.gzPipe results into a shell command
\eOpen the last query in $EDITOR, run it on save
\e file.sqlEdit a file, then execute it
\i script.sqlRun a file, paths relative to the shell cwd
\ir script.sqlRun a file relative to the including file
\g out.txtSend just this one query to a file
\watch 2Re-run the last query every 2 seconds
\! lsShell out
\sCommand history

\copy runs COPY but streams through the client, so it needs no server-side file permissions. This is the difference people trip over: COPY ... FROM '/tmp/x.csv' reads a file on the server, \copy reads one on your laptop.

\copy orders TO 'orders.csv' WITH (FORMAT csv, HEADER)
\copy orders (customer_id, total) FROM 'in.csv' WITH (FORMAT csv, HEADER)
\copy (SELECT id, email FROM customers) TO 'recent.csv' CSV HEADER

A \copy command must fit on one line. There is no backslash continuation for meta-commands, so a long query goes in a file or through COPY (…) TO STDOUT.

Variables and code generation

\set defines a client-side variable. Reference it as :name for a raw substitution, :'name' to get it quoted as a string literal, and :"name" quoted as an identifier.

\set target_email 'ada@example.com'
SELECT * FROM customers WHERE email = :'target_email';

\set tbl orders
SELECT count(*) FROM :"tbl";

\gexec runs the query, then executes every returned string as SQL. This is the idiomatic way to do a bulk DDL operation without writing a shell loop.

SELECT format('ANALYZE %I.%I;', schemaname, relname)
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
\gexec
VariableMeaning
ON_ERROR_STOP1 aborts a script on the first error, the single most important setting
AUTOCOMMIToff wraps every statement in an explicit transaction
VERBOSITYterse / default / verbose for error detail
SHOW_CONTEXTerrors / always for the context lines under an error
ECHOqueries prints each statement as it runs, all echoes script lines
ECHO_HIDDENon prints the SQL behind \d-style commands
HISTFILEPer-database history, ~/.psql_history-:DBNAME

Scripting psql

For anything non-interactive, -X skips ~/.psqlrc so a colleague's settings cannot change your output, and -v ON_ERROR_STOP=1 makes failure an exit code instead of a warning buried in the log.

psql -X -q -c 'SELECT count(*) FROM orders' -d appdb
psql -X -v ON_ERROR_STOP=1 -f migrate.sql -d appdb
psql -X -q -A -t -F, -c 'SELECT id, email FROM customers' -d appdb > out.csv
psql -X -t -A -c 'SELECT pg_is_in_recovery()' -d appdb   # -> t or f
FlagPurpose
-c 'SQL'Run one command and exit
-f file.sqlRun a file and exit
-AUnaligned output, no column padding
-tTuples only, drop the header and the row count footer
-F,Field separator (with -A)
-qQuiet, suppress SELECT 1-style notices
-XIgnore ~/.psqlrc
-1Wrap the whole -f file in one transaction
-v name=valueSet a psql variable from the shell
-P pager=offSet a \pset option from the command line
--csvProper CSV output, quoting included, better than -A -F,

Prefer --csv over -A -t -F, when values can contain commas or quotes.

A useful ~/.psqlrc, guarded so scripts are unaffected because they pass -X:

\set QUIET 1
\timing on
\x auto
\set ON_ERROR_STOP on
\pset null '(null)'
\set COMP_KEYWORD_CASE upper
\set HISTFILE ~/.psql_history-:DBNAME
\set PROMPT1 '%[%033[1;33m%]%n@%/%R%[%033[0m%]%# '
\unset QUIET