PostgreSQL Cheatsheet
Comprehensive quick reference for PostgreSQL commands, SQL syntax, database operations, performance optimization, and administration. Essential guide for PostgreSQL 14+.
Table of Contents
- Prerequisites
- Installation & Version
- Connection & Authentication
- Database Operations
- Table Operations
- Data Types
- CRUD Operations
- Indexes
- Constraints
- Joins & Queries
- Functions & Stored Procedures
- Transactions
- Performance & Optimization
- Backup & Recovery
- User Management
- Best Practices
- Common Pitfalls
Prerequisites
PostgreSQL Version: This cheatsheet targets PostgreSQL 14+. Some features require specific versions (noted inline).
System Requirements:
- Linux: Most distributions supported
- macOS: 10.15+ (via Homebrew or Postgres.app)
- Windows: Windows 10/11 (via installer or WSL)
Prerequisites:
- Basic SQL knowledge
- Command-line familiarity
- Understanding of relational database concepts
Installation & Version
Installation Commands π§
# Check PostgreSQL versionpsql --version# β psql (PostgreSQL) 15.3
# Connect and check server versionpsql -U postgres -c "SELECT version();"# β PostgreSQL 15.3 on x86_64-pc-linux-gnu
# Check PostgreSQL service status (Linux)sudo systemctl status postgresql# orsudo service postgresql status
# Start PostgreSQL service (Linux)sudo systemctl start postgresql# orsudo service postgresql start
# Stop PostgreSQL service (Linux)sudo systemctl stop postgresql# orsudo service postgresql stop
# Restart PostgreSQL service (Linux)sudo systemctl restart postgresqlPlatform-Specific Installation π¦
# Ubuntu/Debiansudo apt updatesudo apt install postgresql postgresql-contrib
# macOS (using Homebrew)brew install postgresql@15brew services start postgresql@15
# macOS (using Postgres.app)# Download from: https://postgresapp.com/
# Windows# Download installer from: https://www.postgresql.org/download/windows/
# Verify installationpsql --versionConfiguration Files π
# Main configuration file location# macOS (Homebrew): /opt/homebrew/var/postgresql@15/postgresql.conf# Windows: C:\Program Files\PostgreSQL\[version]\data\postgresql.conf
# View configuration file locationpsql -U postgres -c "SHOW config_file;"# β /etc/postgresql/15/main/postgresql.conf
# View data directorypsql -U postgres -c "SHOW data_directory;"# β /var/lib/postgresql/15/main
# Reload configuration without restartsudo systemctl reload postgresql# or via psqlpsql -U postgres -c "SELECT pg_reload_conf();"Connection & Authentication
Connection Methods π
# Connect to default database (postgres)psql -U postgres
# Connect to specific databasepsql -U postgres -d mydb
# Connect with host and portpsql -h localhost -p 5432 -U postgres -d mydb
# Connect using connection stringpsql postgresql://username:password@localhost:5432/dbname
# Connect without password prompt (use .pgpass file)psql -U postgres -d mydb# Create ~/.pgpass file: hostname:port:database:username:password
# Connect and execute commandpsql -U postgres -d mydb -c "SELECT version();"
# Connect and execute SQL filepsql -U postgres -d mydb -f script.sql
# Connect with output formattingpsql -U postgres -d mydb -A -t -c "SELECT * FROM users;"# -A: unaligned output, -t: tuples onlypsql Commands (Meta-Commands) π»
-- List all databases\l-- or\list
-- Connect to database\c mydb-- or\connect mydb
-- List all tables in current database\dt
-- List all tables including system tables\dt+
-- Describe table structure\d table_name-- or\dt+ table_name
-- List all schemas\dn
-- List all users/roles\du
-- Show current databaseSELECT current_database();
-- Show current userSELECT current_user;
-- Show current schemaSHOW search_path;
-- Set schema search pathSET search_path TO schema_name, public;
-- List all functions\df
-- List all views\dv
-- Show table size\d+ table_name
-- Show database size\l+
-- Exit psql\q-- orexit
-- Show help\?-- Help for SQL commands\h SELECTAuthentication Methods π
# View pg_hba.conf (host-based authentication)# Location: Usually in data directory or /etc/postgresql/[version]/main/cat /etc/postgresql/15/main/pg_hba.conf
# Common authentication methods:# trust: No password required# md5: Password encrypted with MD5# scram-sha-256: Password encrypted with SCRAM-SHA-256 (PostgreSQL 10+)# peer: Use OS username# ident: Use ident protocol
# Example pg_hba.conf entries:# local all all trust# host all all 127.0.0.1/32 scram-sha-256# host all all ::1/128 scram-sha-256Database Operations
Create & Drop Databases ποΈ
-- Create databaseCREATE DATABASE mydb;
-- Create database with ownerCREATE DATABASE mydb OWNER username;
-- Create database with encodingCREATE DATABASE mydb WITH ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE template0;
-- Create database if not exists (PostgreSQL 9.1+)-- Note: No IF NOT EXISTS for CREATE DATABASE, use:SELECT 'CREATE DATABASE mydb'WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'mydb')\gexec
-- Drop databaseDROP DATABASE mydb;
-- Drop database if existsDROP DATABASE IF EXISTS mydb;
-- Rename databaseALTER DATABASE oldname RENAME TO newname;
-- List all databasesSELECT datname FROM pg_database;
-- Show database sizeSELECT pg_size_pretty(pg_database_size('mydb'));# β 15 MB
-- Show all database sizesSELECT datname, pg_size_pretty(pg_database_size(datname)) AS sizeFROM pg_databaseORDER BY pg_database_size(datname) DESC;Database Information π
-- Show current databaseSELECT current_database();
-- Show database encodingSHOW server_encoding;# β UTF8
-- Show timezoneSHOW timezone;# β UTC
-- Set timezoneSET timezone TO 'America/New_York';
-- Show all settingsSHOW ALL;
-- Show specific settingSHOW max_connections;# β 100
-- Set configuration parameter (session level)SET work_mem = '256MB';
-- Set configuration parameter (database level)ALTER DATABASE mydb SET work_mem = '256MB';Table Operations
Create Tables ποΈ
-- Basic table creationCREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
-- Table with multiple constraintsCREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id), product_name VARCHAR(100) NOT NULL, quantity INTEGER CHECK (quantity > 0), price DECIMAL(10, 2) NOT NULL CHECK (price >= 0), status VARCHAR(20) DEFAULT 'pending', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
-- Create table with schemaCREATE TABLE public.products ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL);
-- Create table if not exists (PostgreSQL 9.1+)CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username VARCHAR(50));
-- Create table from query resultCREATE TABLE users_backup ASSELECT * FROM users WHERE created_at > '2024-01-01';
-- Create temporary tableCREATE TEMP TABLE temp_data ( id INTEGER, value TEXT);
-- Create unlogged table (faster writes, no WAL, lost on crash)CREATE UNLOGGED TABLE cache_data ( key VARCHAR(100) PRIMARY KEY, value TEXT);Alter Tables π§
-- Add columnALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Add column with default valueALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active';
-- Add column with NOT NULL constraint (requires default)ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0;
-- Drop columnALTER TABLE users DROP COLUMN phone;
-- Drop column if existsALTER TABLE users DROP COLUMN IF EXISTS phone;
-- Rename columnALTER TABLE users RENAME COLUMN email TO email_address;
-- Change column typeALTER TABLE users ALTER COLUMN age TYPE SMALLINT;
-- Change column type with conversionALTER TABLE users ALTER COLUMN age TYPE INTEGER USING age::INTEGER;
-- Set column defaultALTER TABLE users ALTER COLUMN status SET DEFAULT 'inactive';
-- Drop column defaultALTER TABLE users ALTER COLUMN status DROP DEFAULT;
-- Add NOT NULL constraintALTER TABLE users ALTER COLUMN email SET NOT NULL;
-- Remove NOT NULL constraintALTER TABLE users ALTER COLUMN email DROP NOT NULL;
-- Rename tableALTER TABLE users RENAME TO customers;
-- Add constraintALTER TABLE users ADD CONSTRAINT users_email_key UNIQUE (email);
-- Drop constraintALTER TABLE users DROP CONSTRAINT users_email_key;
-- Add foreign key constraintALTER TABLE ordersADD CONSTRAINT orders_user_id_fkeyFOREIGN KEY (user_id) REFERENCES users(id);
-- Add check constraintALTER TABLE ordersADD CONSTRAINT orders_quantity_checkCHECK (quantity > 0);Drop Tables ποΈ
-- Drop tableDROP TABLE users;
-- Drop table if existsDROP TABLE IF EXISTS users;
-- Drop table with cascade (drops dependent objects)DROP TABLE users CASCADE;
-- Truncate table (delete all rows, faster than DELETE)TRUNCATE TABLE users;
-- Truncate table and reset sequencesTRUNCATE TABLE users RESTART IDENTITY;
-- Truncate multiple tablesTRUNCATE TABLE users, orders, products;Table Information π
-- List all tablesSELECT tablename FROM pg_tables WHERE schemaname = 'public';
-- Show table structure\d users
-- Show detailed table information\d+ users
-- Show table sizeSELECT pg_size_pretty(pg_total_relation_size('users'));# β 1024 kB
-- Show table size breakdownSELECT pg_size_pretty(pg_relation_size('users')) AS table_size, pg_size_pretty(pg_indexes_size('users')) AS indexes_size, pg_size_pretty(pg_total_relation_size('users')) AS total_size;
-- Count rows in tableSELECT COUNT(*) FROM users;
-- Show table statisticsSELECT schemaname, tablename, n_live_tup AS row_count, n_dead_tup AS dead_rowsFROM pg_stat_user_tablesWHERE tablename = 'users';Data Types
Numeric Types π’
-- Integer typesSMALLINT -- -32,768 to 32,767 (2 bytes)INTEGER -- -2,147,483,648 to 2,147,483,647 (4 bytes)BIGINT -- -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (8 bytes)SERIAL -- Auto-incrementing INTEGER (1 to 2,147,483,647)BIGSERIAL -- Auto-incrementing BIGINT
-- ExampleCREATE TABLE products ( id SERIAL PRIMARY KEY, price INTEGER NOT NULL, quantity SMALLINT DEFAULT 0, views BIGINT DEFAULT 0);
-- Decimal/Numeric types (exact precision)DECIMAL(10, 2) -- 10 digits total, 2 after decimalNUMERIC(10, 2) -- Same as DECIMALMONEY -- Currency type (deprecated, use NUMERIC)
-- ExampleCREATE TABLE orders ( id SERIAL PRIMARY KEY, total DECIMAL(10, 2) NOT NULL, tax NUMERIC(5, 2) DEFAULT 0.00);
-- Floating point types (approximate)REAL -- 6 decimal digits precision (4 bytes)DOUBLE PRECISION -- 15 decimal digits precision (8 bytes)
-- ExampleCREATE TABLE measurements ( id SERIAL PRIMARY KEY, temperature REAL, precision_value DOUBLE PRECISION);Character Types π
-- Character typesVARCHAR(n) -- Variable length, max n charactersCHAR(n) -- Fixed length, padded with spacesTEXT -- Unlimited length
-- ExampleCREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50) NOT NULL, code CHAR(5), -- Always 5 characters bio TEXT -- Unlimited length);
-- String functionsSELECT LENGTH('hello'); -- β 5SELECT UPPER('hello'); -- β HELLOSELECT LOWER('HELLO'); -- β helloSELECT SUBSTRING('hello' FROM 1 FOR 3); -- β helSELECT REPLACE('hello', 'l', 'L'); -- β heLLoSELECT TRIM(' hello '); -- β helloSELECT CONCAT('hello', ' ', 'world'); -- β hello worldSELECT 'hello' || ' ' || 'world'; -- β hello worldDate & Time Types π
-- Date and time typesDATE -- Date only (YYYY-MM-DD)TIME -- Time only (HH:MM:SS)TIMESTAMP -- Date and time (YYYY-MM-DD HH:MM:SS)TIMESTAMPTZ -- Timestamp with timezoneINTERVAL -- Time interval
-- ExampleCREATE TABLE events ( id SERIAL PRIMARY KEY, event_date DATE NOT NULL, start_time TIME, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, duration INTERVAL);
-- Date/time functionsSELECT CURRENT_DATE; -- β 2024-01-15SELECT CURRENT_TIME; -- β 14:30:00SELECT CURRENT_TIMESTAMP; -- β 2024-01-15 14:30:00SELECT NOW(); -- β Current timestampSELECT EXTRACT(YEAR FROM NOW()); -- β 2024SELECT EXTRACT(MONTH FROM NOW()); -- β 1SELECT EXTRACT(DAY FROM NOW()); -- β 15SELECT DATE_TRUNC('day', NOW()); -- β 2024-01-15 00:00:00SELECT DATE_TRUNC('month', NOW()); -- β 2024-01-01 00:00:00SELECT AGE('2024-01-15', '2020-01-01'); -- β 4 years 14 daysBoolean & Other Types β
-- Boolean typeBOOLEAN -- true, false, or NULL
-- ExampleCREATE TABLE tasks ( id SERIAL PRIMARY KEY, title VARCHAR(100) NOT NULL, completed BOOLEAN DEFAULT FALSE);
-- Boolean operatorsSELECT TRUE AND FALSE; -- β falseSELECT TRUE OR FALSE; -- β trueSELECT NOT TRUE; -- β false
-- JSON types (PostgreSQL 9.2+)JSON -- Textual JSON dataJSONB -- Binary JSON data (faster, indexable)
-- ExampleCREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(100), metadata JSONB);
-- JSON operationsINSERT INTO products (name, metadata)VALUES ('Laptop', '{"brand": "Dell", "ram": 16}');
SELECT metadata->>'brand' FROM products; -- β DellSELECT metadata->'ram' FROM products; -- β 16SELECT metadata @> '{"brand": "Dell"}'::jsonb; -- β true
-- Array typesINTEGER[] -- Array of integersTEXT[] -- Array of textVARCHAR(50)[] -- Array of varchar
-- ExampleCREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100), tags TEXT[]);
INSERT INTO users (name, tags)VALUES ('John', ARRAY['admin', 'developer', 'user']);
SELECT tags[1] FROM users; -- β adminSELECT array_length(tags, 1) FROM users; -- β 3CRUD Operations
INSERT π₯
-- Basic insertINSERT INTO users (username, email)VALUES ('john_doe', 'john@example.com');
-- Insert multiple rowsINSERT INTO users (username, email) VALUES ('alice', 'alice@example.com'), ('bob', 'bob@example.com'), ('charlie', 'charlie@example.com');
-- Insert with default valuesINSERT INTO users (username) VALUES ('dave');-- email will use default, created_at will use CURRENT_TIMESTAMP
-- Insert from SELECT queryINSERT INTO users_backup (username, email)SELECT username, email FROM users WHERE created_at > '2024-01-01';
-- Insert with RETURNING clause (get inserted row)INSERT INTO users (username, email)VALUES ('eve', 'eve@example.com')RETURNING id, username, created_at;
-- Insert with ON CONFLICT (PostgreSQL 9.5+)INSERT INTO users (username, email)VALUES ('john_doe', 'john@example.com')ON CONFLICT (username) DO NOTHING;
-- Insert with ON CONFLICT UPDATEINSERT INTO users (username, email)VALUES ('john_doe', 'newemail@example.com')ON CONFLICT (username)DO UPDATE SET email = EXCLUDED.email, updated_at = CURRENT_TIMESTAMP;SELECT π
-- Basic selectSELECT * FROM users;
-- Select specific columnsSELECT id, username, email FROM users;
-- Select with WHERE clauseSELECT * FROM users WHERE id = 1;SELECT * FROM users WHERE username = 'john_doe';SELECT * FROM users WHERE created_at > '2024-01-01';
-- Select with multiple conditionsSELECT * FROM usersWHERE username = 'john_doe' AND email LIKE '%@example.com';
-- Select with OR conditionSELECT * FROM usersWHERE username = 'john_doe' OR username = 'alice';
-- Select with IN clauseSELECT * FROM users WHERE id IN (1, 2, 3);SELECT * FROM users WHERE username IN ('john', 'alice', 'bob');
-- Select with NOT INSELECT * FROM users WHERE id NOT IN (1, 2, 3);
-- Select with BETWEENSELECT * FROM users WHERE id BETWEEN 1 AND 100;SELECT * FROM users WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31';
-- Select with LIKE (pattern matching)SELECT * FROM users WHERE username LIKE 'john%'; -- Starts with 'john'SELECT * FROM users WHERE username LIKE '%doe'; -- Ends with 'doe'SELECT * FROM users WHERE username LIKE '%john%'; -- Contains 'john'SELECT * FROM users WHERE username ILIKE 'JOHN%'; -- Case-insensitive
-- Select with IS NULL / IS NOT NULLSELECT * FROM users WHERE email IS NULL;SELECT * FROM users WHERE email IS NOT NULL;
-- Select with DISTINCTSELECT DISTINCT username FROM users;SELECT DISTINCT ON (username) * FROM users ORDER BY username, created_at DESC;
-- Select with ORDER BYSELECT * FROM users ORDER BY username ASC;SELECT * FROM users ORDER BY created_at DESC;SELECT * FROM users ORDER BY username ASC, email DESC;
-- Select with LIMIT and OFFSETSELECT * FROM users LIMIT 10;SELECT * FROM users LIMIT 10 OFFSET 20; -- Skip first 20, get next 10
-- Select with aggregate functionsSELECT COUNT(*) FROM users;SELECT COUNT(DISTINCT username) FROM users;SELECT AVG(age) FROM users;SELECT SUM(price) FROM orders;SELECT MIN(created_at) FROM users;SELECT MAX(created_at) FROM users;
-- Select with GROUP BYSELECT status, COUNT(*)FROM ordersGROUP BY status;
SELECT user_id, COUNT(*), SUM(price)FROM ordersGROUP BY user_id;
-- Select with HAVING (filter groups)SELECT user_id, COUNT(*)FROM ordersGROUP BY user_idHAVING COUNT(*) > 5;
-- Select with CASE expressionSELECT username, CASE WHEN age < 18 THEN 'Minor' WHEN age < 65 THEN 'Adult' ELSE 'Senior' END AS age_groupFROM users;UPDATE βοΈ
-- Basic updateUPDATE users SET email = 'newemail@example.com' WHERE id = 1;
-- Update multiple columnsUPDATE usersSET email = 'newemail@example.com', updated_at = CURRENT_TIMESTAMPWHERE id = 1;
-- Update with subqueryUPDATE ordersSET status = 'shipped'WHERE user_id IN (SELECT id FROM users WHERE username = 'john_doe');
-- Update with JOIN (PostgreSQL 9.1+)UPDATE orders oSET status = 'cancelled'FROM users uWHERE o.user_id = u.id AND u.username = 'john_doe';
-- Update with RETURNING clauseUPDATE usersSET email = 'newemail@example.com'WHERE id = 1RETURNING id, username, email;
-- Update all rows (use with caution!)UPDATE users SET updated_at = CURRENT_TIMESTAMP;-- β οΈ Always use WHERE clause unless you intend to update all rowsDELETE ποΈ
-- Basic deleteDELETE FROM users WHERE id = 1;
-- Delete with multiple conditionsDELETE FROM usersWHERE username = 'john_doe' AND email IS NULL;
-- Delete with subqueryDELETE FROM ordersWHERE user_id IN (SELECT id FROM users WHERE created_at < '2020-01-01');
-- Delete with JOIN (PostgreSQL 9.1+)DELETE FROM orders oUSING users uWHERE o.user_id = u.id AND u.username = 'john_doe';
-- Delete all rows (use with caution!)DELETE FROM users;-- β οΈ Prefer TRUNCATE for deleting all rows (faster)
-- Delete with RETURNING clauseDELETE FROM usersWHERE id = 1RETURNING id, username, email;Indexes
Create Indexes π
-- Basic indexCREATE INDEX idx_users_username ON users(username);
-- Unique indexCREATE UNIQUE INDEX idx_users_email_unique ON users(email);
-- Composite index (multiple columns)CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
-- Partial index (index subset of rows)CREATE INDEX idx_active_users ON users(username)WHERE status = 'active';
-- Expression index (index on expression result)CREATE INDEX idx_users_lower_username ON users(LOWER(username));
-- Index with specific methodCREATE INDEX idx_users_email_hash ON users USING HASH(email);CREATE INDEX idx_users_location_gist ON locations USING GIST(geom);
-- Concurrent index creation (doesn't block writes)CREATE INDEX CONCURRENTLY idx_users_username ON users(username);
-- Full-text search index (GIN)CREATE INDEX idx_products_description_ginON products USING GIN(to_tsvector('english', description));Index Information π
-- List all indexesSELECT indexname, tablename FROM pg_indexes WHERE schemaname = 'public';
-- Show indexes for a table\d users
-- Show index sizeSELECT pg_size_pretty(pg_relation_size('idx_users_username'));# β 64 kB
-- Show index usage statisticsSELECT schemaname, tablename, indexname, idx_scan AS index_scans, idx_tup_read AS tuples_read, idx_tup_fetch AS tuples_fetchedFROM pg_stat_user_indexesWHERE tablename = 'users';
-- Check if index is being usedEXPLAIN ANALYZE SELECT * FROM users WHERE username = 'john_doe';Drop Indexes ποΈ
-- Drop indexDROP INDEX idx_users_username;
-- Drop index if existsDROP INDEX IF EXISTS idx_users_username;
-- Drop index concurrently (doesn't block reads)DROP INDEX CONCURRENTLY idx_users_username;
-- Reindex (rebuild index)REINDEX INDEX idx_users_username;
-- Reindex table (rebuild all indexes)REINDEX TABLE users;
-- Reindex databaseREINDEX DATABASE mydb;Constraints
Primary Key π
-- Add primary key on creationCREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50));
-- Add primary key constraintALTER TABLE users ADD PRIMARY KEY (id);
-- Composite primary keyCREATE TABLE order_items ( order_id INTEGER, product_id INTEGER, quantity INTEGER, PRIMARY KEY (order_id, product_id));
-- Drop primary keyALTER TABLE users DROP CONSTRAINT users_pkey;Foreign Key π
-- Add foreign key on creationCREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), total DECIMAL(10, 2));
-- Add foreign key with optionsCREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, total DECIMAL(10, 2));
-- Add foreign key constraintALTER TABLE ordersADD CONSTRAINT orders_user_id_fkeyFOREIGN KEY (user_id) REFERENCES users(id);
-- Foreign key with ON DELETE CASCADEALTER TABLE ordersADD CONSTRAINT orders_user_id_fkeyFOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
-- Foreign key with ON DELETE SET NULLALTER TABLE ordersADD CONSTRAINT orders_user_id_fkeyFOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
-- Drop foreign keyALTER TABLE orders DROP CONSTRAINT orders_user_id_fkey;Unique Constraint β¨
-- Add unique constraint on creationCREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE, email VARCHAR(100) UNIQUE);
-- Add unique constraintALTER TABLE users ADD CONSTRAINT users_username_unique UNIQUE (username);
-- Composite unique constraintCREATE TABLE user_roles ( user_id INTEGER, role_id INTEGER, UNIQUE (user_id, role_id));
-- Drop unique constraintALTER TABLE users DROP CONSTRAINT users_username_unique;Check Constraint β
-- Add check constraint on creationCREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(100), price DECIMAL(10, 2) CHECK (price >= 0), quantity INTEGER CHECK (quantity >= 0));
-- Add check constraintALTER TABLE productsADD CONSTRAINT products_price_check CHECK (price >= 0);
-- Drop check constraintALTER TABLE products DROP CONSTRAINT products_price_check;Not Null Constraint π«
-- Add NOT NULL on creationCREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL);
-- Add NOT NULL constraintALTER TABLE users ALTER COLUMN username SET NOT NULL;
-- Remove NOT NULL constraintALTER TABLE users ALTER COLUMN username DROP NOT NULL;Joins & Queries
INNER JOIN π
-- Basic inner joinSELECT u.username, o.totalFROM users uINNER JOIN orders o ON u.id = o.user_id;
-- Inner join with WHERESELECT u.username, o.totalFROM users uINNER JOIN orders o ON u.id = o.user_idWHERE o.total > 100;
-- Multiple inner joinsSELECT u.username, o.total, p.nameFROM users uINNER JOIN orders o ON u.id = o.user_idINNER JOIN products p ON o.product_id = p.id;LEFT JOIN β¬ οΈ
-- Left join (all rows from left table)SELECT u.username, o.totalFROM users uLEFT JOIN orders o ON u.id = o.user_id;
-- Left join with WHERE (filters NULLs)SELECT u.username, o.totalFROM users uLEFT JOIN orders o ON u.id = o.user_idWHERE o.total IS NOT NULL;
-- Left join to find users without ordersSELECT u.usernameFROM users uLEFT JOIN orders o ON u.id = o.user_idWHERE o.id IS NULL;RIGHT JOIN β‘οΈ
-- Right join (all rows from right table)SELECT u.username, o.totalFROM users uRIGHT JOIN orders o ON u.id = o.user_id;
-- Right join to find orders without usersSELECT o.id, o.totalFROM users uRIGHT JOIN orders o ON u.id = o.user_idWHERE u.id IS NULL;FULL OUTER JOIN βοΈ
-- Full outer join (all rows from both tables)SELECT u.username, o.totalFROM users uFULL OUTER JOIN orders o ON u.id = o.user_id;
-- Full outer join to find mismatchesSELECT u.username, o.totalFROM users uFULL OUTER JOIN orders o ON u.id = o.user_idWHERE u.id IS NULL OR o.id IS NULL;CROSS JOIN βοΈ
-- Cross join (Cartesian product)SELECT u.username, p.nameFROM users uCROSS JOIN products p;
-- Equivalent to:SELECT u.username, p.nameFROM users u, products p;Self Join π
-- Self join (join table to itself)SELECT e.name AS employee, m.name AS managerFROM employees eLEFT JOIN employees m ON e.manager_id = m.id;Subqueries π
-- Subquery in WHERE clauseSELECT * FROM usersWHERE id IN (SELECT user_id FROM orders WHERE total > 100);
-- Subquery in SELECT clauseSELECT username, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_countFROM users;
-- Subquery with EXISTSSELECT * FROM users uWHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.total > 100);
-- Correlated subquerySELECT username, (SELECT MAX(total) FROM orders WHERE orders.user_id = users.id) AS max_orderFROM users;
-- Subquery in FROM clause (derived table)SELECT u.username, order_stats.totalFROM users uJOIN ( SELECT user_id, SUM(total) AS total FROM orders GROUP BY user_id) order_stats ON u.id = order_stats.user_id;Common Table Expressions (CTEs) π
-- Basic CTEWITH active_users AS ( SELECT * FROM users WHERE status = 'active')SELECT * FROM active_users;
-- Multiple CTEsWITH active_users AS ( SELECT * FROM users WHERE status = 'active' ), user_orders AS ( SELECT user_id, COUNT(*) AS order_count FROM orders GROUP BY user_id )SELECT u.username, uo.order_countFROM active_users uLEFT JOIN user_orders uo ON u.id = uo.user_id;
-- Recursive CTE (for hierarchical data)WITH RECURSIVE employee_hierarchy AS ( -- Base case SELECT id, name, manager_id, 1 AS level FROM employees WHERE manager_id IS NULL
UNION ALL
-- Recursive case SELECT e.id, e.name, e.manager_id, eh.level + 1 FROM employees e JOIN employee_hierarchy eh ON e.manager_id = eh.id)SELECT * FROM employee_hierarchy;Functions & Stored Procedures
Built-in Functions π οΈ
-- String functionsSELECT LENGTH('hello'); -- β 5SELECT UPPER('hello'); -- β HELLOSELECT LOWER('HELLO'); -- β helloSELECT SUBSTRING('hello' FROM 1 FOR 3); -- β helSELECT REPLACE('hello', 'l', 'L'); -- β heLLoSELECT TRIM(' hello '); -- β helloSELECT CONCAT('hello', ' ', 'world'); -- β hello worldSELECT POSITION('world' IN 'hello world'); -- β 7
-- Numeric functionsSELECT ABS(-10); -- β 10SELECT ROUND(3.14159, 2); -- β 3.14SELECT CEIL(3.1); -- β 4SELECT FLOOR(3.9); -- β 3SELECT MOD(10, 3); -- β 1SELECT POWER(2, 3); -- β 8SELECT SQRT(16); -- β 4
-- Date functionsSELECT CURRENT_DATE; -- β 2024-01-15SELECT CURRENT_TIME; -- β 14:30:00SELECT CURRENT_TIMESTAMP; -- β 2024-01-15 14:30:00SELECT NOW(); -- β Current timestampSELECT EXTRACT(YEAR FROM NOW()); -- β 2024SELECT DATE_TRUNC('day', NOW()); -- β 2024-01-15 00:00:00SELECT AGE('2024-01-15', '2020-01-01'); -- β 4 years 14 days
-- Aggregate functionsSELECT COUNT(*) FROM users;SELECT AVG(price) FROM products;SELECT SUM(total) FROM orders;SELECT MIN(created_at) FROM users;SELECT MAX(created_at) FROM users;SELECT STRING_AGG(username, ', ') FROM users;User-Defined Functions π¦
-- Create function (returns scalar)CREATE OR REPLACE FUNCTION get_user_count()RETURNS INTEGER AS $$BEGIN RETURN (SELECT COUNT(*) FROM users);END;$$ LANGUAGE plpgsql;
-- Call functionSELECT get_user_count();
-- Function with parametersCREATE OR REPLACE FUNCTION get_user_by_id(user_id INTEGER)RETURNS TABLE(id INTEGER, username VARCHAR, email VARCHAR) AS $$BEGIN RETURN QUERY SELECT users.id, users.username, users.email FROM users WHERE users.id = user_id;END;$$ LANGUAGE plpgsql;
-- Call functionSELECT * FROM get_user_by_id(1);
-- Function with default parametersCREATE OR REPLACE FUNCTION greet_user(name VARCHAR DEFAULT 'Guest')RETURNS TEXT AS $$BEGIN RETURN 'Hello, ' || name || '!';END;$$ LANGUAGE plpgsql;
-- Function with OUT parametersCREATE OR REPLACE FUNCTION divide_numbers( a NUMERIC, b NUMERIC, OUT result NUMERIC, OUT remainder NUMERIC) AS $$BEGIN result := a / b; remainder := MOD(a, b);END;$$ LANGUAGE plpgsql;
-- Call functionSELECT * FROM divide_numbers(10, 3);Stored Procedures π―
-- Create stored procedure (PostgreSQL 11+)CREATE OR REPLACE PROCEDURE update_user_email( user_id INTEGER, new_email VARCHAR)LANGUAGE plpgsqlAS $$BEGIN UPDATE users SET email = new_email, updated_at = CURRENT_TIMESTAMP WHERE id = user_id;
IF NOT FOUND THEN RAISE EXCEPTION 'User with id % not found', user_id; END IF;END;$$;
-- Call procedureCALL update_user_email(1, 'newemail@example.com');
-- Procedure with transaction controlCREATE OR REPLACE PROCEDURE transfer_funds( from_account INTEGER, to_account INTEGER, amount DECIMAL)LANGUAGE plpgsqlAS $$BEGIN -- Debit from source account UPDATE accounts SET balance = balance - amount WHERE id = from_account;
-- Credit to destination account UPDATE accounts SET balance = balance + amount WHERE id = to_account;
-- If any update fails, transaction will rollbackEND;$$;Triggers π¬
-- Create trigger functionCREATE OR REPLACE FUNCTION update_updated_at_column()RETURNS TRIGGER AS $$BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW;END;$$ LANGUAGE plpgsql;
-- Create triggerCREATE TRIGGER update_users_updated_atBEFORE UPDATE ON usersFOR EACH ROWEXECUTE FUNCTION update_updated_at_column();
-- Trigger for audit loggingCREATE OR REPLACE FUNCTION audit_user_changes()RETURNS TRIGGER AS $$BEGIN INSERT INTO user_audit_log (user_id, action, changed_at) VALUES (NEW.id, TG_OP, CURRENT_TIMESTAMP); RETURN NEW;END;$$ LANGUAGE plpgsql;
CREATE TRIGGER users_audit_triggerAFTER INSERT OR UPDATE OR DELETE ON usersFOR EACH ROWEXECUTE FUNCTION audit_user_changes();
-- Drop triggerDROP TRIGGER IF EXISTS update_users_updated_at ON users;Transactions
Transaction Control πΌ
-- Start transactionBEGIN;
-- OrSTART TRANSACTION;
-- Commit transactionCOMMIT;
-- Rollback transactionROLLBACK;
-- Example transactionBEGIN; INSERT INTO users (username, email) VALUES ('john', 'john@example.com'); INSERT INTO orders (user_id, total) VALUES (1, 100.00);COMMIT;
-- Transaction with rollback on errorBEGIN; INSERT INTO users (username, email) VALUES ('john', 'john@example.com'); -- If error occurs, rollback ROLLBACK;Transaction Isolation Levels π
-- Set transaction isolation levelBEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- Available isolation levels:-- READ UNCOMMITTED (not available in PostgreSQL, treated as READ COMMITTED)-- READ COMMITTED (default)-- REPEATABLE READ-- SERIALIZABLE
-- ExampleBEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; SELECT * FROM users WHERE id = 1; -- Other operationsCOMMIT;
-- Show current isolation levelSHOW transaction_isolation;# β read committedSavepoints π
-- Create savepointBEGIN; INSERT INTO users (username, email) VALUES ('john', 'john@example.com'); SAVEPOINT sp1;
INSERT INTO orders (user_id, total) VALUES (1, 100.00); -- If error, rollback to savepoint ROLLBACK TO SAVEPOINT sp1;
-- Continue transaction INSERT INTO orders (user_id, total) VALUES (1, 200.00);COMMIT;
-- Release savepointRELEASE SAVEPOINT sp1;Locking π
-- Row-level lockingBEGIN; SELECT * FROM users WHERE id = 1 FOR UPDATE; -- Row is locked until transaction commits UPDATE users SET email = 'new@example.com' WHERE id = 1;COMMIT;
-- Share lock (allows reads, prevents writes)BEGIN; SELECT * FROM users WHERE id = 1 FOR SHARE;COMMIT;
-- Table-level lockingLOCK TABLE users IN EXCLUSIVE MODE;-- Operations...UNLOCK TABLE users;
-- Advisory locks (application-level)SELECT pg_advisory_lock(12345);-- Critical section...SELECT pg_advisory_unlock(12345);Performance & Optimization
EXPLAIN & ANALYZE π
-- Basic EXPLAINEXPLAIN SELECT * FROM users WHERE username = 'john_doe';
-- EXPLAIN with ANALYZE (executes query)EXPLAIN ANALYZE SELECT * FROM users WHERE username = 'john_doe';
-- EXPLAIN with verbose outputEXPLAIN (ANALYZE, BUFFERS, VERBOSE)SELECT * FROM users WHERE username = 'john_doe';
-- EXPLAIN with format optionsEXPLAIN (FORMAT JSON) SELECT * FROM users WHERE username = 'john_doe';EXPLAIN (FORMAT XML) SELECT * FROM users WHERE username = 'john_doe';Query Optimization Tips β‘
-- β
Use indexes on frequently queried columnsCREATE INDEX idx_users_username ON users(username);
-- β
Use EXPLAIN ANALYZE to identify slow queriesEXPLAIN ANALYZE SELECT * FROM users WHERE username = 'john_doe';
-- β
Use LIMIT when you don't need all rowsSELECT * FROM users LIMIT 10; -- β
GoodSELECT * FROM users; -- β Bad if you only need 10 rows
-- β
Use specific columns instead of SELECT *SELECT id, username FROM users; -- β
GoodSELECT * FROM users; -- β Bad if you don't need all columns
-- β
Use WHERE to filter earlySELECT * FROM orders WHERE user_id = 1 AND total > 100; -- β
Good
-- β
Use JOINs instead of subqueries when possible-- β
Good: JOINSELECT u.username, o.totalFROM users uJOIN orders o ON u.id = o.user_id;
-- β Bad: Subquery (usually slower)SELECT username, (SELECT total FROM orders WHERE user_id = users.id)FROM users;
-- β
Use EXISTS instead of COUNT(*) for existence checksSELECT * FROM users uWHERE EXISTS (SELECT 1 FROM orders WHERE user_id = u.id); -- β
Good
SELECT * FROM users uWHERE (SELECT COUNT(*) FROM orders WHERE user_id = u.id) > 0; -- β BadVacuum & Analyze π§Ή
-- VACUUM (reclaims storage, updates statistics)VACUUM;
-- VACUUM specific tableVACUUM users;
-- VACUUM FULL (rewrites table, requires exclusive lock)VACUUM FULL users;
-- VACUUM ANALYZE (vacuum and update statistics)VACUUM ANALYZE users;
-- ANALYZE (update statistics for query planner)ANALYZE users;
-- ANALYZE all tablesANALYZE;
-- Show table statisticsSELECT schemaname, tablename, n_live_tup, n_dead_tup, last_vacuum, last_autovacuum, last_analyze, last_autoanalyzeFROM pg_stat_user_tablesWHERE tablename = 'users';Connection Pooling π
-- Show current connectionsSELECT pid, usename, application_name, client_addr, state, query_start, state_changeFROM pg_stat_activityWHERE datname = 'mydb';
-- Show connection countSELECT COUNT(*) FROM pg_stat_activity WHERE datname = 'mydb';
-- Kill specific connectionSELECT pg_terminate_backend(pid)FROM pg_stat_activityWHERE pid = 12345;
-- Show max connections settingSHOW max_connections;# β 100Backup & Recovery
pg_dump (Backup) πΎ
# Backup single databasepg_dump -U postgres -d mydb > backup.sql
# Backup with custom format (compressed, allows selective restore)pg_dump -U postgres -d mydb -Fc -f backup.dump
# Backup with compressionpg_dump -U postgres -d mydb | gzip > backup.sql.gz
# Backup specific schema onlypg_dump -U postgres -d mydb -n public > backup.sql
# Backup specific tables onlypg_dump -U postgres -d mydb -t users -t orders > backup.sql
# Backup data only (no schema)pg_dump -U postgres -d mydb --data-only > data_only.sql
# Backup schema only (no data)pg_dump -U postgres -d mydb --schema-only > schema_only.sql
# Backup with verbose outputpg_dump -U postgres -d mydb -v > backup.sqlpg_restore (Restore) π
# Restore from SQL filepsql -U postgres -d mydb < backup.sql
# Restore from custom formatpg_restore -U postgres -d mydb backup.dump
# Restore with verbose outputpg_restore -U postgres -d mydb -v backup.dump
# Restore specific tablespg_restore -U postgres -d mydb -t users backup.dump
# Restore with clean (drop objects first)pg_restore -U postgres -d mydb --clean backup.dump
# Restore schema onlypg_restore -U postgres -d mydb --schema-only backup.dumppg_dumpall (Backup All) π
# Backup all databases (including globals)pg_dumpall -U postgres > all_databases.sql
# Backup globals only (users, roles, etc.)pg_dumpall -U postgres --globals-only > globals.sql
# Restore all databasespsql -U postgres < all_databases.sqlContinuous Archiving & Point-in-Time Recovery π
# Enable WAL archiving in postgresql.confwal_level = replicaarchive_mode = onarchive_command = 'cp %p /path/to/archive/%f'
# Base backuppg_basebackup -D /path/to/backup -Ft -z -P
# Restore from base backup and WAL# 1. Copy base backup to data directory# 2. Create recovery.conf (PostgreSQL 12+) or use postgresql.auto.conf# 3. Start PostgreSQL serverUser Management
Create & Manage Users π€
-- Create user (role)CREATE USER john WITH PASSWORD 'secure_password';
-- Create user with additional optionsCREATE USER aliceWITH PASSWORD 'secure_password' CREATEDB CREATEROLE LOGIN;
-- Create superuserCREATE USER admin WITH SUPERUSER PASSWORD 'secure_password';
-- Alter user passwordALTER USER john WITH PASSWORD 'new_password';
-- Alter user optionsALTER USER john WITH CREATEDB;ALTER USER john WITH NOCREATEDB;
-- Rename userALTER USER john RENAME TO john_doe;
-- Drop userDROP USER john;
-- Drop user if existsDROP USER IF EXISTS john;
-- List all usersSELECT usename FROM pg_user;
-- Show user privileges\du-- orSELECT * FROM pg_roles;Roles & Privileges π
-- Create roleCREATE ROLE developers;
-- Grant privileges to roleGRANT SELECT, INSERT, UPDATE ON users TO developers;GRANT ALL PRIVILEGES ON DATABASE mydb TO developers;
-- Grant role to userGRANT developers TO john;
-- Revoke privilegesREVOKE SELECT ON users FROM developers;
-- Revoke roleREVOKE developers FROM john;
-- Create role with loginCREATE ROLE app_user WITH LOGIN PASSWORD 'password';
-- Grant schema usageGRANT USAGE ON SCHEMA public TO developers;
-- Grant all privileges on all tablesGRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO developers;
-- Grant privileges on future tablesALTER DEFAULT PRIVILEGES IN SCHEMA publicGRANT ALL PRIVILEGES ON TABLES TO developers;
-- Show role membershipsSELECT r.rolname AS role, m.rolname AS memberFROM pg_roles rJOIN pg_auth_members am ON r.oid = am.roleidJOIN pg_roles m ON am.member = m.oid;Database Privileges ποΈ
-- Grant database privilegesGRANT CONNECT ON DATABASE mydb TO john;GRANT CREATE ON DATABASE mydb TO developers;
-- Revoke database privilegesREVOKE CREATE ON DATABASE mydb FROM developers;
-- Show database privilegesSELECT datname, dataclFROM pg_databaseWHERE datname = 'mydb';Best Practices
β Doβs
-- β
Always use parameterized queries to prevent SQL injection-- β
Good: Use placeholdersPREPARE get_user AS SELECT * FROM users WHERE id = $1;EXECUTE get_user(1);
-- β
Use transactions for related operationsBEGIN; INSERT INTO users (username) VALUES ('john'); INSERT INTO orders (user_id, total) VALUES (1, 100);COMMIT;
-- β
Use appropriate data typesCREATE TABLE users ( id SERIAL PRIMARY KEY, -- β
SERIAL for auto-increment username VARCHAR(50) NOT NULL, -- β
VARCHAR with limit email VARCHAR(100) UNIQUE, -- β
UNIQUE constraint created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- β
Default timestamp);
-- β
Create indexes on foreign keys and frequently queried columnsCREATE INDEX idx_orders_user_id ON orders(user_id);CREATE INDEX idx_users_username ON users(username);
-- β
Use EXPLAIN ANALYZE to optimize queriesEXPLAIN ANALYZE SELECT * FROM users WHERE username = 'john';
-- β
Use LIMIT when you don't need all rowsSELECT * FROM users LIMIT 10;
-- β
Use specific column names instead of SELECT *SELECT id, username, email FROM users; -- β
Good
-- β
Use meaningful table and column namesCREATE TABLE user_accounts ( -- β
Good account_id SERIAL PRIMARY KEY, user_name VARCHAR(50));
-- β
Use constraints to enforce data integrityCREATE TABLE orders ( id SERIAL PRIMARY KEY, quantity INTEGER CHECK (quantity > 0), -- β
Check constraint user_id INTEGER REFERENCES users(id) -- β
Foreign key);
-- β
Use VACUUM and ANALYZE regularlyVACUUM ANALYZE users;
-- β
Use connection pooling for applications-- Use pgBouncer or similar tools
-- β
Backup databases regularly-- Schedule pg_dump in cron or similarβ Donβts
-- β Don't use SELECT * in production queriesSELECT * FROM users; -- β Bad: fetches unnecessary columns
-- β Don't forget to use transactions for related operationsINSERT INTO users (username) VALUES ('john');INSERT INTO orders (user_id, total) VALUES (1, 100);-- β Bad: If second insert fails, first insert remains
-- β Don't use string concatenation for SQL queries (SQL injection risk)-- β Bad: Vulnerable to SQL injection-- SELECT * FROM users WHERE username = '$username';
-- β Don't create indexes on every columnCREATE INDEX idx_users_id ON users(id); -- β Bad: Primary key already indexedCREATE INDEX idx_users_created_at ON users(created_at); -- β Bad if rarely queried
-- β Don't use TEXT for fixed-length stringsCREATE TABLE users ( username TEXT -- β Bad: Use VARCHAR(50) if max length is known);
-- β Don't ignore NULL values without considering themSELECT * FROM users WHERE email != 'test@example.com';-- β Bad: Doesn't include rows where email IS NULL-- β
Good: SELECT * FROM users WHERE email IS NULL OR email != 'test@example.com';
-- β Don't use functions in WHERE clauses without indexesSELECT * FROM users WHERE LOWER(username) = 'john'; -- β Bad: Can't use index-- β
Good: CREATE INDEX idx_users_lower_username ON users(LOWER(username));
-- β Don't forget to set up proper backup strategy-- β Bad: No backups configured
-- β Don't use superuser for application connections-- β Bad: Application connects as postgres user-- β
Good: Create dedicated user with minimal privileges
-- β Don't ignore VACUUM warnings-- PostgreSQL will warn when tables need vacuuming-- β Bad: Ignore warnings-- β
Good: Run VACUUM ANALYZE regularlyCommon Pitfalls
β οΈ NULL Handling
-- β οΈ NULL comparison doesn't work with = or !=SELECT * FROM users WHERE email = NULL; -- β Returns no rowsSELECT * FROM users WHERE email IS NULL; -- β
Correct
SELECT * FROM users WHERE email != 'test@example.com';-- β οΈ Doesn't include rows where email IS NULLSELECT * FROM users WHERE email IS NULL OR email != 'test@example.com'; -- β
Correct
-- β οΈ NULL in arithmetic operationsSELECT 10 + NULL; -- β NULLSELECT COALESCE(price, 0) + 10 FROM products; -- β
Handle NULLβ οΈ String Comparison
-- β οΈ String comparison is case-sensitiveSELECT * FROM users WHERE username = 'John'; -- Won't match 'john'SELECT * FROM users WHERE username ILIKE 'john'; -- β
Case-insensitive
-- β οΈ Trailing spaces matterSELECT * FROM users WHERE username = 'john '; -- Won't match 'john'SELECT * FROM users WHERE TRIM(username) = 'john'; -- β
Handle spacesβ οΈ Date/Time Issues
-- β οΈ Timezone handlingSELECT CURRENT_TIMESTAMP; -- Includes timezoneSELECT NOW(); -- Includes timezoneSELECT CURRENT_DATE; -- No timezone
-- β οΈ Date arithmeticSELECT '2024-01-15'::DATE + INTERVAL '1 day'; -- β
CorrectSELECT '2024-01-15'::DATE + 1; -- β Error: Can't add integer to date
-- β οΈ Timestamp comparisonSELECT * FROM users WHERE created_at = '2024-01-15';-- β οΈ May not match due to time componentSELECT * FROM users WHERE DATE(created_at) = '2024-01-15'; -- β
CorrectSELECT * FROM users WHERE created_at::DATE = '2024-01-15'; -- β
Correctβ οΈ Transaction Issues
-- β οΈ Uncommitted transactions block other operationsBEGIN; SELECT * FROM users WHERE id = 1 FOR UPDATE; -- β οΈ Other transactions trying to update this row will wait -- β οΈ If you forget to COMMIT, lock persistsCOMMIT; -- β
Always commit or rollback
-- β οΈ Long-running transactionsBEGIN; -- Long operation... -- β οΈ Holds locks and prevents VACUUMCOMMIT; -- β
Keep transactions shortβ οΈ Performance Issues
-- β οΈ Missing indexes on foreign keysCREATE TABLE orders ( user_id INTEGER REFERENCES users(id) -- β οΈ No index on user_id);CREATE INDEX idx_orders_user_id ON orders(user_id); -- β
Add index
-- β οΈ Full table scans on large tablesSELECT * FROM users WHERE LOWER(username) = 'john';-- β οΈ Can't use index on usernameCREATE INDEX idx_users_lower_username ON users(LOWER(username)); -- β
Expression index
-- β οΈ N+1 query problem-- β Bad: Multiple queries in loop-- SELECT * FROM users;-- For each user: SELECT * FROM orders WHERE user_id = ?-- β
Good: Single query with JOINSELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id;Critical Warning: Always test queries with EXPLAIN ANALYZE before deploying to production. Missing indexes or inefficient queries can cause severe performance degradation.
Critical Warning: Never run DROP TABLE or TRUNCATE without WHERE clause unless youβre absolutely certain. These operations cannot be easily undone.
Critical Warning: Always use transactions for related operations. If one operation fails, related operations should rollback to maintain data consistency.