πŸ“‹ Cheat Sheets

SQL Cheat Sheet β€” Every Query You Need to Know


Click any item to expand with examples.

πŸ” Querying Data

SELECT essential
SELECT * FROM users;
SELECT name, email FROM users;
SELECT DISTINCT country FROM users;
SELECT name AS full_name FROM users;
SELECT COUNT(*) AS total FROM users;
WHERE essential
SELECT * FROM users WHERE age > 18;
SELECT * FROM users WHERE name = 'Alice';
SELECT * FROM users WHERE age BETWEEN 18 AND 30;
SELECT * FROM users WHERE country IN ('US', 'UK', 'BE');
SELECT * FROM users WHERE name LIKE 'A%';       -- starts with A
SELECT * FROM users WHERE email IS NOT NULL;
SELECT * FROM users WHERE age > 18 AND active = true;
SELECT * FROM users WHERE age > 65 OR role = 'admin';
ORDER BY & LIMIT essential
SELECT * FROM users ORDER BY name ASC;
SELECT * FROM users ORDER BY created_at DESC;
SELECT * FROM users ORDER BY country, name;
SELECT * FROM users LIMIT 10;
SELECT * FROM users LIMIT 10 OFFSET 20;  -- pagination
GROUP BY & HAVING essential
-- Count users per country
SELECT country, COUNT(*) AS total
FROM users
GROUP BY country;

β€” Average age per country, only countries with 10+ users SELECT country, AVG(age) AS avg_age FROM users GROUP BY country HAVING COUNT(*) >= 10;

β€” Aggregate functions: COUNT, SUM, AVG, MIN, MAX

πŸ”— Joins

INNER JOIN essential
Returns only rows that have matching values in both tables.
SELECT users.name, orders.total
FROM users
INNER JOIN orders ON users.id = orders.user_id;
LEFT JOIN essential
Returns all rows from the left table, with matching rows from the right (NULL if no match).
-- All users, even those without orders
SELECT users.name, orders.total
FROM users
LEFT JOIN orders ON users.id = orders.user_id;
RIGHT JOIN / FULL OUTER JOIN useful
-- All orders, even without matching users
SELECT users.name, orders.total
FROM users
RIGHT JOIN orders ON users.id = orders.user_id;

β€” All rows from both tables SELECT users.name, orders.total FROM users FULL OUTER JOIN orders ON users.id = orders.user_id;

Self JOIN advanced
-- Find employees and their managers
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

✏️ Modifying Data

INSERT essential
INSERT INTO users (name, email, age)
VALUES ('Alice', 'alice@example.com', 30);

β€” Multiple rows INSERT INTO users (name, email) VALUES (β€˜Bob’, β€˜bob@example.com’), (β€˜Charlie’, β€˜charlie@example.com’);

β€” Insert from select INSERT INTO archive (name, email) SELECT name, email FROM users WHERE active = false;

UPDATE essential
UPDATE users SET active = false WHERE last_login < '2025-01-01';
UPDATE users SET age = age + 1;  -- increment all
UPDATE users SET name = 'Bob', email = 'new@email.com' WHERE id = 5;
⚠️ Always use WHERE β€” without it, you update every row.
DELETE essential
DELETE FROM users WHERE id = 5;
DELETE FROM users WHERE active = false;
DELETE FROM users;  -- deletes ALL rows (careful!)
TRUNCATE TABLE users;  -- faster, resets auto-increment
⚠️ Always use WHERE β€” without it, you delete everything.

πŸ—οΈ Schema

CREATE TABLE essential
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  age INTEGER DEFAULT 0,
  active BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

β€” With foreign key CREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), total DECIMAL(10, 2) NOT NULL );

ALTER TABLE useful
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users DROP COLUMN phone;
ALTER TABLE users RENAME COLUMN name TO full_name;
ALTER TABLE users ALTER COLUMN age SET DEFAULT 18;
ALTER TABLE users ADD CONSTRAINT unique_email UNIQUE (email);
Indexes performance
CREATE INDEX idx_users_email ON users(email);
CREATE UNIQUE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
DROP INDEX idx_users_email;
Add indexes on columns you frequently filter or join on.

πŸš€ Advanced

Subqueries advanced
-- Users who have placed orders
SELECT * FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);

β€” Users with above-average age SELECT * FROM users WHERE age > (SELECT AVG(age) FROM users);

β€” Correlated subquery SELECT name, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count FROM users;

CTEs (Common Table Expressions) advanced
WITH active_users AS (
  SELECT * FROM users WHERE active = true
),
user_orders AS (
  SELECT user_id, SUM(total) AS total_spent
  FROM orders GROUP BY user_id
)
SELECT au.name, uo.total_spent
FROM active_users au
JOIN user_orders uo ON au.id = uo.user_id;
CTEs make complex queries readable. Use them instead of nested subqueries.
Window functions advanced
-- Row number
SELECT name, ROW_NUMBER() OVER (ORDER BY created_at) AS row_num FROM users;

β€” Rank (with gaps for ties) SELECT name, score, RANK() OVER (ORDER BY score DESC) FROM leaderboard;

β€” Running total SELECT date, amount, SUM(amount) OVER (ORDER BY date) AS running_total FROM sales;

β€” Previous/next row SELECT date, amount, LAG(amount) OVER (ORDER BY date) AS prev_amount, LEAD(amount) OVER (ORDER BY date) AS next_amount FROM sales;

β€” Partition SELECT department, name, salary, AVG(salary) OVER (PARTITION BY department) AS dept_avg FROM employees;

UNION / INTERSECT / EXCEPT useful
-- Combine results (removes duplicates)
SELECT name FROM customers UNION SELECT name FROM suppliers;

β€” Keep duplicates SELECT name FROM customers UNION ALL SELECT name FROM suppliers;

β€” Only in both SELECT name FROM customers INTERSECT SELECT name FROM suppliers;

β€” In first but not second SELECT name FROM customers EXCEPT SELECT name FROM suppliers;

Quick Reference

What you want to doSQL
Get all rowsSELECT * FROM table
FilterWHERE condition
SortORDER BY column DESC
Limit resultsLIMIT 10 OFFSET 20
CountSELECT COUNT(*) FROM table
Group & countGROUP BY column
Join tablesJOIN table2 ON t1.id = t2.fk
Remove duplicatesSELECT DISTINCT column
Check for NULLWHERE column IS NULL
Pattern matchWHERE name LIKE '%pattern%'
InsertINSERT INTO table (cols) VALUES (vals)
UpdateUPDATE table SET col = val WHERE ...
DeleteDELETE FROM table WHERE ...