📖 Guide
SQL Cheat Sheet — Complete Reference
Complete SQL reference covering queries, joins, aggregation, subqueries, indexes, and database management.
137 commands across 12 categories
SELECT QueriesFiltering (WHERE)JoinsAggregation (GROUP BY)SubqueriesINSERT / UPDATE / DELETETable ManagementIndexes & ConstraintsString FunctionsDate FunctionsWindow FunctionsCommon Patterns
SELECT Queries
| Command | Description |
|---|---|
SELECT * FROM table_name; | Select all columns from a table |
SELECT col1, col2 FROM table_name; | Select specific columns |
SELECT DISTINCT col FROM table_name; | Select unique values only (remove duplicates) |
SELECT col AS alias FROM table_name;e.g. SELECT first_name AS name, salary * 12 AS annual_salary FROM employees; | Rename a column in the output |
SELECT * FROM t ORDER BY col ASC; | Sort results in ascending order (default) |
SELECT * FROM t ORDER BY col DESC; | Sort results in descending order |
SELECT * FROM t ORDER BY col1 ASC, col2 DESC; | Sort by multiple columns |
SELECT * FROM t LIMIT n; | Return only the first N rows |
SELECT * FROM t LIMIT n OFFSET m;e.g. SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 40; | Skip M rows, then return N rows (pagination) |
SELECT * FROM t FETCH FIRST n ROWS ONLY; | SQL standard alternative to LIMIT |
Filtering (WHERE)
| Command | Description |
|---|---|
WHERE col = valuee.g. SELECT * FROM users WHERE status = 'active'; | Filter rows where column equals a value |
WHERE col != value / WHERE col <> value | Not equal comparison |
WHERE col > / >= / < / <= value | Numeric comparison operators |
WHERE col BETWEEN a AND be.g. SELECT * FROM orders WHERE total BETWEEN 100 AND 500; | Filter within a range (inclusive) |
WHERE col IN (v1, v2, v3)e.g. SELECT * FROM users WHERE country IN ('US', 'UK', 'DE'); | Match any value in a list |
WHERE col NOT IN (v1, v2) | Exclude specific values |
WHERE col LIKE patterne.g. SELECT * FROM users WHERE email LIKE '%@gmail.com'; | Pattern match with % (any chars) and _ (one char) |
WHERE col ILIKE patterne.g. SELECT * FROM products WHERE name ILIKE '%phone%'; | Case-insensitive LIKE (PostgreSQL) |
WHERE col ~ regexe.g. SELECT * FROM users WHERE email ~ '^[a-z]+@'; | POSIX regex match (PostgreSQL) |
WHERE col IS NULL / IS NOT NULL | Check for NULL values |
WHERE cond1 AND cond2 | Both conditions must be true |
WHERE cond1 OR cond2 | At least one condition must be true |
WHERE NOT condition | Negate a condition |
WHERE EXISTS (subquery)e.g. SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id); | True if the subquery returns any rows |
Joins
| Command | Description |
|---|---|
INNER JOIN t2 ON t1.col = t2.cole.g. SELECT u.name, o.total FROM users u INNER JOIN orders o ON u.id = o.user_id; | Return rows with matching values in both tables |
LEFT JOIN t2 ON t1.col = t2.col | Return all rows from left table, matching rows from right (NULL if no match) |
RIGHT JOIN t2 ON t1.col = t2.col | Return all rows from right table, matching rows from left |
FULL OUTER JOIN t2 ON t1.col = t2.col | Return all rows from both tables (NULL where no match) |
CROSS JOIN t2 | Cartesian product — every combination of rows |
t1 JOIN t2 USING (col)e.g. SELECT * FROM orders JOIN products USING (product_id); | Join on a column with the same name in both tables |
NATURAL JOIN t2 | Automatically join on all columns with matching names |
LEFT JOIN t2 ON ... WHERE t2.col IS NULLe.g. SELECT u.* FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL; | Anti-join — find rows in t1 with no match in t2 |
Self-join: JOIN same_table AS aliase.g. SELECT e.name, m.name AS manager FROM employees e LEFT JOIN employees m ON e.manager_id = m.id; | Join a table to itself |
Aggregation (GROUP BY)
| Command | Description |
|---|---|
COUNT(*) | Count all rows (including NULLs) |
COUNT(col) | Count non-NULL values in a column |
COUNT(DISTINCT col) | Count distinct non-NULL values |
SUM(col) | Sum all values in a column |
AVG(col) | Average of all values |
MIN(col) / MAX(col) | Minimum or maximum value |
GROUP BY col1, col2e.g. SELECT country, COUNT(*) FROM users GROUP BY country; | Group rows by column values for aggregation |
HAVING conditione.g. SELECT country, COUNT(*) AS cnt FROM users GROUP BY country HAVING COUNT(*) > 10; | Filter groups (like WHERE but for aggregated results) |
STRING_AGG(col, delimiter)e.g. SELECT department, STRING_AGG(name, ', ') FROM employees GROUP BY department; | Concatenate values into a string (PostgreSQL) |
ARRAY_AGG(col) | Collect values into an array (PostgreSQL) |
BOOL_OR(col) / BOOL_AND(col) | Aggregate boolean values (PostgreSQL) |
Subqueries
| Command | Description |
|---|---|
WHERE col IN (SELECT ...)e.g. SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > 1000); | Filter using results from a subquery |
WHERE col = (SELECT ...)e.g. SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products); | Compare with a scalar subquery (must return one value) |
SELECT (SELECT ...) AS col | Use a scalar subquery as a column |
FROM (SELECT ...) AS aliase.g. SELECT * FROM (SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id) AS order_counts WHERE cnt > 5; | Use a subquery as a derived table |
WITH cte AS (SELECT ...) SELECT ... FROM ctee.g. WITH active_users AS (
SELECT * FROM users WHERE status = 'active'
)
SELECT * FROM active_users WHERE created_at > '2024-01-01'; | Common Table Expression (CTE) — named subquery |
WITH RECURSIVE cte AS (...)e.g. WITH RECURSIVE tree AS (
SELECT id, name, parent_id, 0 AS depth FROM categories WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id, t.depth + 1 FROM categories c JOIN tree t ON c.parent_id = t.id
)
SELECT * FROM tree; | Recursive CTE for hierarchical/tree data |
LATERAL (SELECT ...)e.g. SELECT u.name, latest.total FROM users u, LATERAL (SELECT total FROM orders WHERE user_id = u.id ORDER BY created_at DESC LIMIT 1) AS latest; | Lateral subquery — can reference columns from preceding tables (PostgreSQL) |
INSERT / UPDATE / DELETE
| Command | Description |
|---|---|
INSERT INTO t (col1, col2) VALUES (v1, v2);e.g. INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); | Insert a single row |
INSERT INTO t (cols) VALUES (...), (...), (...); | Insert multiple rows in one statement |
INSERT INTO t (cols) SELECT ... FROM other; | Insert rows from a SELECT query |
INSERT INTO t ... RETURNING *;e.g. INSERT INTO users (name) VALUES ('Bob') RETURNING id, name; | Insert and return the inserted row(s) (PostgreSQL) |
INSERT INTO t ... ON CONFLICT (col) DO NOTHING; | Upsert — skip if duplicate key (PostgreSQL) |
INSERT INTO t ... ON CONFLICT (col) DO UPDATE SET col = EXCLUDED.col;e.g. INSERT INTO users (email, name) VALUES ('a@b.com', 'Alice') ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name; | Upsert — update on duplicate key (PostgreSQL) |
UPDATE t SET col = value WHERE condition;e.g. UPDATE users SET status = 'inactive' WHERE last_login < '2023-01-01'; | Update rows matching a condition |
UPDATE t SET col1 = v1, col2 = v2 WHERE ...; | Update multiple columns at once |
UPDATE t SET col = col + 1 WHERE ...; | Update using current value (increment) |
UPDATE t SET ... FROM other WHERE t.id = other.id; | Update using a join (PostgreSQL) |
UPDATE t SET ... RETURNING *; | Update and return the modified rows (PostgreSQL) |
DELETE FROM t WHERE condition; | Delete rows matching a condition |
DELETE FROM t; | Delete all rows (use TRUNCATE for large tables) |
TRUNCATE TABLE t; | Remove all rows quickly (resets auto-increment, non-transactional in some DBs) |
TRUNCATE TABLE t RESTART IDENTITY CASCADE; | Truncate and reset sequences, cascade to dependent tables (PostgreSQL) |
Table Management
| Command | Description |
|---|---|
CREATE TABLE t (col1 type, col2 type);e.g. CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
created_at TIMESTAMP DEFAULT NOW()
); | Create a new table |
CREATE TABLE IF NOT EXISTS t (...); | Create table only if it doesn't already exist |
CREATE TABLE t AS SELECT ...; | Create a table from a query result |
ALTER TABLE t ADD COLUMN col type; | Add a new column to an existing table |
ALTER TABLE t DROP COLUMN col; | Remove a column from a table |
ALTER TABLE t ALTER COLUMN col TYPE new_type; | Change a column's data type (PostgreSQL) |
ALTER TABLE t ALTER COLUMN col SET NOT NULL; | Add a NOT NULL constraint |
ALTER TABLE t ALTER COLUMN col SET DEFAULT value; | Set a default value for a column |
ALTER TABLE t RENAME COLUMN old TO new; | Rename a column |
ALTER TABLE t RENAME TO new_name; | Rename a table |
DROP TABLE t; | Delete a table and all its data |
DROP TABLE IF EXISTS t CASCADE; | Drop table if exists, including dependent objects |
Indexes & Constraints
| Command | Description |
|---|---|
CREATE INDEX idx_name ON t (col); | Create an index on a column (B-tree by default) |
CREATE UNIQUE INDEX idx_name ON t (col); | Create a unique index (enforces uniqueness) |
CREATE INDEX idx_name ON t (col1, col2); | Create a composite (multi-column) index |
CREATE INDEX idx_name ON t USING GIN (col); | Create a GIN index (good for arrays, JSONB, full-text) |
CREATE INDEX CONCURRENTLY idx_name ON t (col); | Create index without locking the table (PostgreSQL) |
DROP INDEX idx_name; | Remove an index |
PRIMARY KEY (col) | Define a primary key constraint |
UNIQUE (col) | Ensure all values in a column are unique |
NOT NULL | Prevent NULL values in a column |
CHECK (condition)e.g. CHECK (price > 0) | Enforce a custom constraint |
REFERENCES other_table (col)e.g. user_id INTEGER REFERENCES users (id) ON DELETE CASCADE | Foreign key constraint |
ALTER TABLE t ADD CONSTRAINT name UNIQUE (col); | Add a constraint to an existing table |
ALTER TABLE t DROP CONSTRAINT name; | Remove a constraint |
String Functions
| Command | Description |
|---|---|
LENGTH(s) / CHAR_LENGTH(s) | Get string length |
UPPER(s) / LOWER(s) | Convert to uppercase or lowercase |
TRIM(s) / LTRIM(s) / RTRIM(s) | Remove leading/trailing whitespace |
SUBSTRING(s FROM start FOR length)e.g. SUBSTRING('Hello World' FROM 1 FOR 5) -- 'Hello' | Extract part of a string |
POSITION(sub IN s) | Find position of substring (1-indexed) |
REPLACE(s, from, to) | Replace all occurrences of a substring |
CONCAT(s1, s2, ...) / s1 || s2 | Concatenate strings |
LEFT(s, n) / RIGHT(s, n) | Get first/last N characters |
LPAD(s, length, fill) / RPAD(s, length, fill)e.g. LPAD('42', 5, '0') -- '00042' | Pad string to a specified length |
INITCAP(s) | Capitalize first letter of each word (PostgreSQL) |
REGEXP_REPLACE(s, pattern, replacement)e.g. REGEXP_REPLACE('abc123', '[0-9]+', 'NUM') | Replace using regex (PostgreSQL) |
SPLIT_PART(s, delimiter, n)e.g. SPLIT_PART('a.b.c', '.', 2) -- 'b' | Get Nth part of a delimited string (PostgreSQL) |
Date Functions
| Command | Description |
|---|---|
NOW() / CURRENT_TIMESTAMP | Current date and time |
CURRENT_DATE | Current date (without time) |
CURRENT_TIME | Current time (without date) |
EXTRACT(field FROM timestamp)e.g. EXTRACT(YEAR FROM created_at)
EXTRACT(DOW FROM NOW()) -- day of week | Get a specific part of a date/time |
DATE_TRUNC(field, timestamp)e.g. DATE_TRUNC('month', created_at) -- start of month | Truncate to specified precision (PostgreSQL) |
timestamp + INTERVAL '1 day'e.g. NOW() - INTERVAL '30 days'
NOW() + INTERVAL '2 hours' | Add/subtract intervals from timestamps |
AGE(timestamp1, timestamp2)e.g. AGE(NOW(), created_at) | Difference between two timestamps as interval (PostgreSQL) |
TO_CHAR(timestamp, format)e.g. TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI:SS') | Format a timestamp as a string (PostgreSQL) |
TO_TIMESTAMP(text, format) | Parse a string into a timestamp (PostgreSQL) |
date1 - date2 | Difference between two dates (returns integer days in PostgreSQL) |
GENERATE_SERIES(start, stop, step)e.g. SELECT generate_series('2024-01-01'::date, '2024-12-31'::date, '1 month'::interval); | Generate a series of dates (PostgreSQL) |
Window Functions
| Command | Description |
|---|---|
ROW_NUMBER() OVER (ORDER BY col)e.g. SELECT name, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rank FROM employees; | Assign sequential row numbers |
RANK() OVER (ORDER BY col) | Rank with gaps for ties (1, 2, 2, 4) |
DENSE_RANK() OVER (ORDER BY col) | Rank without gaps for ties (1, 2, 2, 3) |
NTILE(n) OVER (ORDER BY col)e.g. NTILE(4) OVER (ORDER BY salary) AS quartile | Divide rows into N roughly equal groups |
OVER (PARTITION BY col ORDER BY col2)e.g. SELECT department, name, salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees; | Window function partitioned by groups |
SUM(col) OVER (ORDER BY col ROWS BETWEEN ...)e.g. SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total | Running/cumulative sum with frame specification |
LAG(col, n, default) OVER (...)e.g. LAG(salary, 1) OVER (ORDER BY hire_date) AS prev_salary | Access a value from N rows before the current row |
LEAD(col, n, default) OVER (...) | Access a value from N rows after the current row |
FIRST_VALUE(col) OVER (...) | Get the first value in the window frame |
LAST_VALUE(col) OVER (...) | Get the last value in the window frame |
Common Patterns
| Command | Description |
|---|---|
CASE WHEN cond THEN val ... ELSE default ENDe.g. SELECT name,
CASE
WHEN salary > 100000 THEN 'high'
WHEN salary > 50000 THEN 'mid'
ELSE 'low'
END AS bracket
FROM employees; | Conditional logic in SQL |
COALESCE(val1, val2, ...)e.g. SELECT COALESCE(nickname, first_name, 'Anonymous') AS display_name FROM users; | Return first non-NULL value |
NULLIF(a, b)e.g. SELECT total / NULLIF(count, 0) AS average FROM stats; | Return NULL if a equals b (useful to avoid division by zero) |
CAST(expr AS type) / expr::typee.g. SELECT '42'::INTEGER;
SELECT CAST('2024-01-01' AS DATE); | Type casting (PostgreSQL supports :: shorthand) |
SELECT ... UNION ALL SELECT ... | Combine results from two queries (keeps duplicates) |
SELECT ... UNION SELECT ... | Combine results and remove duplicates |
SELECT ... INTERSECT SELECT ... | Return rows common to both queries |
SELECT ... EXCEPT SELECT ... | Return rows in first query but not in second |
EXPLAIN ANALYZE SELECT ...; | Show query execution plan with actual timings |
CREATE VIEW v AS SELECT ...;e.g. CREATE VIEW active_users AS SELECT * FROM users WHERE status = 'active'; | Create a reusable named query |
CREATE MATERIALIZED VIEW mv AS SELECT ...; | Create a cached view (PostgreSQL) — must be refreshed manually |
REFRESH MATERIALIZED VIEW mv; | Refresh data in a materialized view |
BEGIN; ... COMMIT; / ROLLBACK;e.g. BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; | Transaction — group statements atomically |
📖 Free, searchable command reference. Bookmark this page for quick access.