PostgreSQL Cheat Sheet

A complete reference of PostgreSQL syntax with 61 interactive examples. Copy code snippets or click “Try It” to run them directly in your browser.

Creating & Modifying Tables

CREATE TABLE

·

Create a new table with columns and constraints.

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT UNIQUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Tip: Use SERIAL or BIGSERIAL for auto-incrementing IDs.

CREATE TABLE IF NOT EXISTS

·

Create a table only if it does not already exist.

CREATE TABLE IF NOT EXISTS logs (
  id SERIAL PRIMARY KEY,
  message TEXT,
  level TEXT DEFAULT 'info'
);

ALTER TABLE ADD

·

Add a new column to an existing table.

ALTER TABLE users ADD COLUMN phone TEXT;

ALTER TABLE ALTER COLUMN

·

Modify a column type or constraints.

ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(100);

ALTER TABLE RENAME

·

Rename a table or column.

ALTER TABLE users RENAME COLUMN name TO full_name;

DROP TABLE

·

Delete a table and all its data.

DROP TABLE IF EXISTS temp_data CASCADE;

Tip: Use CASCADE to also drop dependent objects.

CREATE INDEX

·

Create an index to speed up queries.

CREATE INDEX idx_users_email ON users(email);

Tip: PostgreSQL supports B-tree, Hash, GIN, GiST, and BRIN indexes.

CREATE VIEW

·

Create a virtual table based on a SELECT query.

CREATE VIEW active_users AS
SELECT id, name, email FROM users
WHERE created_at > NOW() - INTERVAL '30 days';

CREATE SCHEMA

·

Create a namespace for database objects.

CREATE SCHEMA sales;
CREATE TABLE sales.orders (id SERIAL PRIMARY KEY);

Tip: Schemas help organize tables and control access.

Adding & Changing Data

INSERT

·

Add new rows to a table.

INSERT INTO users (name, email)
VALUES ('Dave Wilson', 'dave@example.com')
RETURNING *;

Tip: Use RETURNING to get the inserted row back.

INSERT Multiple Rows

·

Insert multiple rows in a single statement.

INSERT INTO products (name, category, price, stock) VALUES
  ('Headphones', 'Electronics', 149.99, 100),
  ('Webcam', 'Electronics', 89.99, 60)
RETURNING *;

UPDATE

·

Modify existing rows in a table.

UPDATE orders SET status = 'shipped'
WHERE id = 3
RETURNING *;

Tip: Always use WHERE to avoid updating all rows.

DELETE

·

Remove rows from a table.

DELETE FROM orders WHERE status = 'pending'
RETURNING *;

Tip: Use RETURNING to see what was deleted.

UPSERT (INSERT ON CONFLICT)

·

Insert a row or update it if a conflict occurs.

INSERT INTO users (id, name, email)
VALUES (1, 'Alice Updated', 'alice.new@example.com')
ON CONFLICT (id) DO UPDATE SET
  name = EXCLUDED.name,
  email = EXCLUDED.email
RETURNING *;

Tip: EXCLUDED refers to the row that would have been inserted.

TRUNCATE

·

Quickly remove all rows from a table.

-- TRUNCATE is faster than DELETE for removing all rows
-- TRUNCATE TABLE logs;

Tip: TRUNCATE is faster than DELETE but cannot be rolled back easily.

COPY

·

Bulk import/export data from files.

-- Import from CSV
-- COPY users FROM '/tmp/users.csv' WITH CSV HEADER;

-- Export to CSV
-- COPY (SELECT * FROM users) TO '/tmp/export.csv' WITH CSV HEADER;

Tip: Use \copy in psql for client-side file access.

Querying Data

SELECT

·

Retrieve data from one or more tables.

SELECT name, email FROM users;

SELECT *

·

Select all columns from a table.

SELECT * FROM products;

Tip: Avoid SELECT * in production; specify needed columns.

WHERE

·

Filter rows based on conditions.

SELECT * FROM orders WHERE price > 50 AND status = 'completed';

ORDER BY

·

Sort results by one or more columns.

SELECT * FROM products ORDER BY price DESC NULLS LAST;

Tip: PostgreSQL allows NULLS FIRST/LAST to control NULL ordering.

LIMIT & OFFSET

·

Limit the number of results and skip rows.

SELECT * FROM products ORDER BY price DESC LIMIT 3 OFFSET 1;

Tip: For large offsets, consider keyset pagination instead.

GROUP BY

·

Group rows by column values for aggregation.

SELECT status, COUNT(*) as count, SUM(price) as total
FROM orders GROUP BY status;

HAVING

·

Filter groups after GROUP BY (like WHERE for groups).

SELECT user_id, COUNT(*) as order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 1;

DISTINCT / DISTINCT ON

·

Return only unique values.

SELECT DISTINCT category FROM products;

-- First product per category (PostgreSQL-specific)
-- SELECT DISTINCT ON (category) * FROM products ORDER BY category, price;

Tip: DISTINCT ON is a PostgreSQL extension for getting first row per group.

INNER JOIN

·

Return rows where there is a match in both tables.

SELECT users.name, orders.product, orders.price
FROM orders
INNER JOIN users ON orders.user_id = users.id;

LEFT JOIN

·

Return all rows from left table, with matches from right.

SELECT users.name, orders.product
FROM users
LEFT JOIN orders ON users.id = orders.user_id;

Tip: Unmatched right table columns will be NULL.

Subquery

·

Use a query inside another query.

SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE price > 100);

CTE (WITH clause)

·

Define a temporary named result set for the query.

WITH high_value_orders AS (
  SELECT user_id, SUM(price) as total
  FROM orders
  GROUP BY user_id
  HAVING SUM(price) > 100
)
SELECT users.name, high_value_orders.total
FROM high_value_orders
JOIN users ON users.id = high_value_orders.user_id;

Tip: CTEs make complex queries more readable.

UNION

·

Combine results from multiple SELECT statements.

SELECT name, 'user' as type FROM users
UNION ALL
SELECT name, 'product' as type FROM products;

Tip: UNION removes duplicates; UNION ALL keeps them.

Window Functions

·

Perform calculations across related rows.

SELECT name, price,
  ROW_NUMBER() OVER (ORDER BY price DESC) as rank,
  SUM(price) OVER () as total
FROM products;

Tip: Window functions don't collapse rows like GROUP BY.

Built-in Functions

COUNT

·

Count the number of rows.

SELECT COUNT(*) as total_orders FROM orders;

SUM & AVG

·

Calculate the sum or average of a column.

SELECT
  SUM(price) as total_revenue,
  AVG(price) as avg_order_value
FROM orders;

MIN & MAX

·

Find the minimum or maximum value.

SELECT MIN(price) as cheapest, MAX(price) as most_expensive
FROM products;

COALESCE

·

Return the first non-NULL value.

SELECT name, COALESCE(email, 'no email') as email
FROM users;

Tip: Useful for providing default values for NULL columns.

NULLIF

·

Return NULL if two values are equal.

SELECT name, NULLIF(stock, 0) as stock_or_null
FROM products;

Tip: Useful for avoiding division by zero: x / NULLIF(y, 0)

CASE

·

Conditional logic in SQL (like if/else).

SELECT product, price,
  CASE
    WHEN price > 500 THEN 'expensive'
    WHEN price > 100 THEN 'moderate'
    ELSE 'affordable'
  END as price_tier
FROM orders;

String Functions

·

Manipulate text values.

SELECT
  UPPER(name) as uppercase,
  LENGTH(name) as name_length,
  SPLIT_PART(email, '@', 1) as username
FROM users;

Date Functions

·

Work with dates and times.

SELECT
  NOW() as current_time,
  CURRENT_DATE as today,
  CURRENT_DATE + INTERVAL '7 days' as next_week,
  EXTRACT(YEAR FROM NOW()) as current_year;

JSON Functions

·

Extract and manipulate JSON data.

SELECT '{"name": "Alice", "age": 30}'::jsonb->>'name' as name;

Tip: Use JSONB over JSON for better performance and indexing.

Array Functions

·

Work with PostgreSQL arrays.

SELECT ARRAY[1, 2, 3] as arr,
  ARRAY[1, 2, 3] @> ARRAY[2] as contains_two,
  array_length(ARRAY[1, 2, 3], 1) as len;

Tip: Arrays can be indexed with GIN for fast lookups.

generate_series

·

Generate a series of values.

SELECT generate_series(1, 5) as num;

-- Date series
-- SELECT generate_series(
--   '2024-01-01'::date,
--   '2024-01-07'::date,
--   '1 day'::interval
-- ) as day;

Tip: Great for generating test data or filling date gaps.

Data Rules & Constraints

PRIMARY KEY

·

Uniquely identify each row in a table.

CREATE TABLE employees (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  department_id INTEGER
);

Tip: Use SERIAL, BIGSERIAL, or GENERATED ALWAYS AS IDENTITY.

FOREIGN KEY

·

Link to a row in another table.

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
);

Tip: ON DELETE options: CASCADE, SET NULL, SET DEFAULT, RESTRICT.

UNIQUE

·

Ensure all values in a column are different.

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT UNIQUE,
  username TEXT UNIQUE
);

NOT NULL

·

Prevent NULL values in a column.

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  price DECIMAL(10,2) NOT NULL
);

DEFAULT

·

Set a default value when none is provided.

CREATE TABLE logs (
  id SERIAL PRIMARY KEY,
  message TEXT,
  level TEXT DEFAULT 'info',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CHECK

·

Validate data against a condition.

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  price DECIMAL CHECK (price > 0),
  stock INTEGER CHECK (stock >= 0)
);

EXCLUDE

·

Prevent overlapping values (PostgreSQL-specific).

-- Prevent overlapping date ranges
-- CREATE TABLE reservations (
--   room_id INTEGER,
--   during TSRANGE,
--   EXCLUDE USING gist (room_id WITH =, during WITH &&)
-- );

Tip: Requires btree_gist extension for non-range types.

Transactions

BEGIN

·

Start a new transaction.

BEGIN;

INSERT INTO users (name, email) VALUES ('Test', 'test@example.com');

-- Changes are not visible to other connections yet

Tip: PostgreSQL defaults to READ COMMITTED isolation.

COMMIT

·

Save all changes made in the transaction.

BEGIN;
INSERT INTO users (name, email) VALUES ('Test', 'test@example.com');
COMMIT;

SELECT * FROM users WHERE name = 'Test';

ROLLBACK

·

Undo all changes made in the transaction.

BEGIN;
DELETE FROM users;
ROLLBACK;

-- Users table is unchanged
SELECT * FROM users;

SAVEPOINT

·

Create a point to rollback to within a transaction.

BEGIN;
INSERT INTO users (name, email) VALUES ('A', 'a@test.com');
SAVEPOINT my_savepoint;
INSERT INTO users (name, email) VALUES ('B', 'b@test.com');
ROLLBACK TO SAVEPOINT my_savepoint;
COMMIT;

-- Only user 'A' was inserted
SELECT * FROM users WHERE email LIKE '%test.com';

LOCK / SELECT FOR UPDATE

·

Explicitly lock rows or tables.

-- Lock specific rows for update
-- SELECT * FROM orders WHERE id = 1 FOR UPDATE;

-- Skip already-locked rows (for queue processing)
-- SELECT * FROM jobs WHERE status = 'pending'
-- FOR UPDATE SKIP LOCKED LIMIT 1;

Tip: FOR UPDATE SKIP LOCKED is great for job queues.

Utilities & Advanced

Information Schema

·

Query database metadata.

SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position;

EXPLAIN ANALYZE

·

Show query execution plan with actual timing.

EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 1;

Tip: Use EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) for more detail.

VACUUM

·

Reclaim storage and update statistics.

-- Update statistics for query planner
-- VACUUM ANALYZE users;

-- Reclaim space (locks table)
-- VACUUM FULL users;

Tip: VACUUM FULL locks the table; regular VACUUM does not.

pg_stat Views

·

Monitor database activity and performance.

-- See active queries
-- SELECT pid, state, query FROM pg_stat_activity;

-- Table statistics
-- SELECT relname, seq_scan, idx_scan FROM pg_stat_user_tables;

Extensions

·

Add extra functionality to PostgreSQL.

-- Enable UUID generation
-- CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Enable full-text search
-- CREATE EXTENSION IF NOT EXISTS pg_trgm;

Tip: Popular: uuid-ossp, pgcrypto, pg_trgm, postgis.

LIKE & ILIKE

·

Pattern matching with wildcards.

SELECT * FROM users WHERE email ILIKE '%@example.com';

Tip: ILIKE is PostgreSQL-specific case-insensitive matching.

Regular Expressions

·

Match patterns using regex.

SELECT * FROM users WHERE email ~* '^[a-z]+@example\.com$';

Tip: PostgreSQL uses POSIX regex syntax.

LATERAL JOIN

·

Reference columns from preceding tables in subquery.

SELECT users.name, recent_order.product
FROM users
CROSS JOIN LATERAL (
  SELECT product FROM orders
  WHERE orders.user_id = users.id
  ORDER BY created_at DESC LIMIT 1
) recent_order;

Tip: LATERAL lets subqueries reference earlier table columns.