psql CLI
psql -U user -d dbnameConnect to database as userpsql -h host -p 5432 -U user -d dbConnect to remote host on specific port\lList all databases\c dbnameSwitch to / connect to database\dtList tables in current schema\d tablenameDescribe table (columns, types, indexes)\d+ tablenameDetailed table description with storage info\diList indexes\dvList views\dfList functions\duList roles / users\dnList schemas\timingToggle query execution time display\xToggle expanded output mode (one column per line)\eOpen last query in $EDITOR\i file.sqlExecute SQL from file\o file.txtSend output to file (empty \o to stop)\copy t FROM file.csv CSV HEADERClient-side COPY from local file\qQuit psql\?List all psql backslash commands\h SELECTShow SQL syntax help for SELECTData Types
INTEGER / BIGINT / SMALLINT4-byte / 8-byte / 2-byte signed integerSERIAL / BIGSERIALAuto-incrementing integer (creates sequence)col BIGINT GENERATED ALWAYS AS IDENTITYSQL-standard auto-increment (preferred over SERIAL in PG 10+)NUMERIC(p, s) / DECIMALExact decimal, p total digits, s decimal placesREAL / DOUBLE PRECISION4-byte / 8-byte floating-point numberVARCHAR(n) / TEXT / CHAR(n)Variable-length / unlimited text / fixed-lengthBOOLEANTRUE / FALSE / NULLDATECalendar date (no time zone)TIMETime of day (no date)TIMESTAMPDate and time without time zoneTIMESTAMPTZDate and time with time zone (recommended)INTERVALTime span, e.g. INTERVAL '1 day 2 hours'UUID128-bit universally unique identifierJSON / JSONBJSON text / binary JSON (indexed, faster queries)ARRAYArray of any base type, e.g. INTEGER[]BYTEABinary data (byte array)INET / CIDR / MACADDRIP address / IP network / MAC addressCREATE TYPE mood AS ENUM ('happy', 'sad')Custom enum typeTSVECTOR / TSQUERYFull-text search document / queryDDL - Schema & Tables
CREATE DATABASE nameCreate a new databaseDROP DATABASE nameDrop database (cannot be undone)CREATE TABLE t (id SERIAL PRIMARY KEY, name TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW())Create table with common column patternsALTER TABLE t ADD COLUMN col TYPEAdd a columnALTER TABLE t DROP COLUMN colRemove a columnALTER TABLE t ALTER COLUMN col TYPE newtype USING col::newtypeChange column type with castALTER TABLE t RENAME COLUMN old TO newRename a columnALTER TABLE t RENAME TO new_nameRename a tableALTER TABLE t ADD CONSTRAINT name UNIQUE (col)Add unique constraintALTER TABLE t ADD FOREIGN KEY (col) REFERENCES other(id) ON DELETE CASCADEAdd foreign key with cascade deleteALTER TABLE t ADD CHECK (col > 0)Add check constraintDROP TABLE tDrop tableDROP TABLE IF EXISTS t CASCADEDrop table and dependent objects if existsTRUNCATE TABLE t RESTART IDENTITY CASCADERemove all rows, reset sequences, cascadeCREATE VIEW v AS SELECT ...Create a viewCREATE OR REPLACE VIEW v AS SELECT ...Create or replace a viewCREATE MATERIALIZED VIEW mv AS SELECT ...Create materialized view (cached result)REFRESH MATERIALIZED VIEW mvRefresh materialized view dataREFRESH MATERIALIZED VIEW CONCURRENTLY mvRefresh without locking reads (requires unique index)CREATE SCHEMA nameCreate a schema namespaceQueries & Filtering
SELECT * FROM tSelect all columnsSELECT col1, col2 FROM tSelect specific columnsSELECT DISTINCT col FROM tUnique values onlyWHERE col = valEquality filterWHERE col != val -- or <>Not equalAND / OR / NOTLogical operatorsIN (1, 2, 3)Match any value in listNOT IN (...)Exclude values in listBETWEEN x AND yInclusive range filterLIKE '%pat%'Pattern match (case-sensitive, % = wildcard)ILIKE '%pat%'Case-insensitive pattern match (PostgreSQL-specific)IS NULL / IS NOT NULLNULL checkLIMIT n OFFSET mReturn n rows starting at row mORDER BY col ASC / DESCSort resultsORDER BY col NULLS FIRST / NULLS LASTControl NULL sort positionFETCH FIRST n ROWS ONLYSQL-standard alternative to LIMITSELECT ... FOR UPDATELock selected rows for updateCASE WHEN cond THEN val ELSE other ENDConditional expressionCOALESCE(a, b, c)Return first non-NULL valueNULLIF(a, b)Return NULL if a = b, else return aJoins
INNER JOIN t2 ON t1.id = t2.fkOnly matching rows from both tablesLEFT JOIN t2 ON t1.id = t2.fkAll rows from t1, matching rows from t2 (NULL if no match)RIGHT JOIN t2 ON t1.id = t2.fkAll rows from t2, matching from t1FULL OUTER JOIN t2 ON t1.id = t2.fkAll rows from both tables, NULLs where no matchCROSS JOIN t2Cartesian product - every row combined with every rowJOIN t2 USING (id)Join on identically named column, deduplicated in outputJOIN LATERAL (SELECT ...) AS sub ON trueSubquery that can reference columns from preceding FROM itemsJOIN t AS t2 ON t.parent_id = t2.idSelf-join (hierarchical data)NATURAL JOIN t2Auto-join on all same-named columns (use with care)Aggregation
COUNT(*)Count all rows including NULLsCOUNT(col)Count non-NULL values in colCOUNT(DISTINCT col)Count distinct non-NULL valuesSUM(col)Sum of non-NULL valuesAVG(col)Average of non-NULL valuesMIN(col) / MAX(col)Minimum / maximum valueGROUP BY colGroup rows by column valueHAVING COUNT(*) > 1Filter groups (like WHERE but for aggregates)COUNT(*) FILTER (WHERE status = 'active')Conditional aggregate - count only rows matching the conditionSTRING_AGG(col, ', ')Concatenate values into string with separatorSTRING_AGG(col, ', ' ORDER BY col)Concatenate with explicit orderARRAY_AGG(col)Aggregate values into an arrayJSON_AGG(row)Aggregate rows into a JSON arrayJSONB_AGG(row)Aggregate rows into a JSONB arrayBOOL_AND(col) / BOOL_OR(col)True if all / any values are truePERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY col)Median (continuous percentile)JSON & JSONB
col->>'key'Get object field as textcol->'key'Get object field as JSONcol#>>'{a,b}'Get nested path value as textcol#>'{a,b}'Get nested path value as JSONjsonb_build_object('k', v, 'k2', v2)Build JSONB object from key-value pairsjsonb_build_array(1, 2, 3)Build JSONB arrayjsonb_set(col, '{key}', '"value"')Set a key in existing JSONBcol || '{"k":"v"}'::jsonbMerge two JSONB objectscol - 'key'Remove key from JSONB objectcol ? 'key'Does key exist in JSONB?col ?| ARRAY['a','b']Any of the keys exist?col ?& ARRAY['a','b']All of the keys exist?col @> '{"k":"v"}'::jsonbJSONB contains this value?col <@ '{"k":"v"}'::jsonbJSONB is contained by this value?jsonb_each(col)Expand JSONB object to (key, value) rowsjsonb_object_keys(col)Return set of top-level keysjsonb_array_elements(col)Expand JSONB array to individual elementsjsonb_array_length(col)Number of elements in JSONB arrayto_jsonb(val)Convert any SQL value to JSONBrow_to_json(t)Convert a row to JSON objectCREATE INDEX ON t USING GIN (col)GIN index for fast JSONB queriesCREATE INDEX ON t USING GIN (col jsonb_path_ops)GIN index optimised for @> operatorArrays
col = ARRAY[1, 2, 3]Array literalcol[1]First element (1-indexed)col[2:4]Array slice elements 2 through 4array_length(col, 1)Length of array along dimension 1cardinality(col)Total number of elements (any dimension)array_append(col, val)Append element to endarray_prepend(val, col)Prepend element to beginningarray_remove(col, val)Remove all occurrences of valarray_replace(col, old, new)Replace all occurrences of old with newarray_cat(a, b)Concatenate two arraysarray_to_string(col, ',')Join array elements into stringstring_to_array('a,b,c', ',')Split string into arrayunnest(col)Expand array to a set of rowscol @> ARRAY[1, 2]Array contains these elements?col && ARRAY[1, 2]Arrays overlap (share any element)?ANY(col) = valValue equals any element in arrayALL(col) > 0Condition holds for all elementsCREATE INDEX ON t USING GIN (col)GIN index for fast array containment queriesWindow Functions
ROW_NUMBER() OVER (ORDER BY col)Unique sequential number per row in partitionRANK() OVER (ORDER BY col)Rank with gaps for ties (1,1,3...)DENSE_RANK() OVER (ORDER BY col)Rank without gaps for ties (1,1,2...)NTILE(4) OVER (ORDER BY col)Divide rows into n buckets (quartiles)LAG(col, 1) OVER (ORDER BY col)Value from previous row (offset 1)LEAD(col, 1) OVER (ORDER BY col)Value from next row (offset 1)FIRST_VALUE(col) OVER (...)First value in the window frameLAST_VALUE(col) OVER (... ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)Last value in partition - must extend frame to partition end (default frame stops at current row)NTH_VALUE(col, 2) OVER (...)Nth value in the window frameSUM(col) OVER (ORDER BY date)Running total (cumulative sum)AVG(col) OVER (ORDER BY date ROWS 6 PRECEDING)7-row moving average (current row + 6 preceding rows)PARTITION BY col ORDER BY colReset window for each partition valueROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWFrame from start of partition to current rowWINDOW w AS (PARTITION BY col ORDER BY col)Named window definition, reuse with OVER wCTEs & Subqueries
WITH cte AS (SELECT ...)Common Table Expression (named subquery)WITH cte1 AS (...), cte2 AS (...)Multiple CTEs in one queryWITH RECURSIVE cte(cols) AS (base UNION ALL recursive)Recursive CTE for hierarchical / graph dataSELECT * FROM (SELECT ...) AS subInline subquery in FROM clauseWHERE col = (SELECT MAX(col) FROM t)Scalar subqueryWHERE col IN (SELECT id FROM other)Subquery in INWHERE EXISTS (SELECT 1 FROM t WHERE ...)Exists check (stops at first match)WHERE NOT EXISTS (...)Rows with no matching related rowLEFT JOIN LATERAL (SELECT ...) AS sub ON trueLATERAL join - subquery references outer rowWITH ins AS (INSERT ... RETURNING *) SELECT * FROM insCTE with data-modifying statementIndexes
CREATE INDEX idx ON t (col)Basic B-tree indexCREATE UNIQUE INDEX idx ON t (col)Unique B-tree indexCREATE INDEX idx ON t (col1, col2)Composite index (order matters)CREATE INDEX idx ON t (col DESC NULLS LAST)Index with sort order matching queryCREATE INDEX CONCURRENTLY idx ON t (col)Build index without locking table writesCREATE INDEX ON t USING GIN (col)GIN index for JSONB / array / full-text searchCREATE INDEX ON t USING GIST (col)GiST index for geometric, range, or full-text typesCREATE INDEX ON t USING HASH (col)Hash index - fast equality onlyCREATE INDEX ON t USING BRIN (col)BRIN index - very large, naturally ordered tablesCREATE INDEX ON t (lower(col))Expression index for case-insensitive queriesCREATE INDEX ON t (col) WHERE active = truePartial index - only indexes matching rowsDROP INDEX CONCURRENTLY idxDrop index without lockingREINDEX TABLE tRebuild all indexes on a tableREINDEX TABLE CONCURRENTLY tRebuild all indexes without locking table (PG 12+)EXPLAIN SELECT ...Show query execution plan (estimated costs)EXPLAIN ANALYZE SELECT ...Show plan with actual execution statsEXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT ...Detailed plan with buffer usageTransactions
BEGINStart a transactionCOMMITCommit current transactionROLLBACKRoll back current transactionSAVEPOINT sp1Create a savepoint within a transactionROLLBACK TO SAVEPOINT sp1Roll back to savepoint (transaction stays open)RELEASE SAVEPOINT sp1Remove a savepointSET TRANSACTION ISOLATION LEVEL READ COMMITTEDDefault level - sees committed rows at each statementSET TRANSACTION ISOLATION LEVEL REPEATABLE READSnapshot at transaction start, no phantom readsSET TRANSACTION ISOLATION LEVEL SERIALIZABLEStrictest - transactions appear fully sequentialSELECT ... FOR UPDATELock rows to prevent concurrent updatesSELECT ... FOR SHAREShared lock - others can read but not updateSELECT ... SKIP LOCKEDSkip locked rows instead of waitingSELECT ... NOWAITFail immediately if lock cannot be acquiredLOCK TABLE t IN EXCLUSIVE MODEExplicit table-level lockInsert, Update, Delete & Upsert
INSERT INTO t (col) VALUES (val)Insert single rowINSERT INTO t (col) VALUES (v1), (v2)Insert multiple rows at onceINSERT INTO t SELECT * FROM srcInsert from query resultINSERT INTO t (...) VALUES (...) RETURNING idInsert and return generated valuesUPDATE t SET col = val WHERE id = 1Update specific rowsUPDATE t SET col = val RETURNING *Update and return modified rowsUPDATE t SET col = src.col FROM src WHERE t.id = src.idUpdate from another tableDELETE FROM t WHERE id = 1Delete specific rowsDELETE FROM t RETURNING *Delete and return removed rowsINSERT INTO t (...) VALUES (...) ON CONFLICT (col) DO UPDATE SET col = EXCLUDED.colUpsert - update on duplicate keyON CONFLICT (col) DO NOTHINGUpsert - ignore on duplicate keyON CONFLICT ON CONSTRAINT constraint_name DO UPDATE ...Upsert targeting a named constraintBackup & Restore
pg_dump dbname > dump.sqlDump database to plain SQL filepg_dump -Fc dbname > dump.pgcDump in custom compressed format (preferred)pg_dump -Fd dbname -f outdir/Dump in directory format (parallelisable)pg_dump -t tablename dbname > table.sqlDump a single tablepg_dump -s dbname > schema.sqlSchema only (no data)pg_dump --data-only dbname > data.sqlData only (no schema)pg_dumpall > all.sqlDump all databases and rolespsql dbname < dump.sqlRestore from plain SQL filepg_restore -d dbname dump.pgcRestore from custom format dumppg_restore --clean -d dbname dump.pgcRestore, dropping objects firstpg_restore -j 4 -d dbname dump.pgcParallel restore with 4 workersvacuumdb -d dbnameRun VACUUM on a databaseVACUUM ANALYZE tReclaim space and update planner statisticspg_basebackup -D /backup --format=tPhysical base backup in tar format