SQL Cheatsheet: Queries Every Developer Needs
April 7, 2026 · 9 min read
SQL is the lingua franca of relational databases. Whether you are using PostgreSQL, MySQL, SQLite, or SQL Server, the core syntax is largely compatible. This cheatsheet covers the queries and patterns you will write repeatedly as a backend developer.
SELECT Basics
SELECT * FROM users; SELECT id, name, email FROM users; SELECT name AS full_name, email FROM users; /* alias */ SELECT DISTINCT country FROM users; /* unique values */ SELECT COUNT(*) FROM users; /* aggregate */
Filtering with WHERE
SELECT * FROM orders WHERE status = 'active';
SELECT * FROM orders WHERE amount > 100 AND status = 'paid';
SELECT * FROM users WHERE country IN ('US', 'CA', 'UK');
SELECT * FROM users WHERE email LIKE '%@gmail.com';
SELECT * FROM users WHERE name LIKE 'Al%'; /* starts with Al */
SELECT * FROM orders WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31';
SELECT * FROM users WHERE deleted_at IS NULL;
SELECT * FROM users WHERE deleted_at IS NOT NULL;Sorting and Limiting
SELECT * FROM products ORDER BY price ASC; SELECT * FROM products ORDER BY price DESC, name ASC; /* multi-column sort */ SELECT * FROM products LIMIT 10; /* first 10 rows */ SELECT * FROM products LIMIT 10 OFFSET 20; /* rows 21-30 (pagination) */ /* PostgreSQL style */ SELECT * FROM products FETCH FIRST 10 ROWS ONLY;
Aggregates and GROUP BY
SELECT status, COUNT(*) AS count FROM orders GROUP BY status; SELECT user_id, SUM(amount) AS total_spent FROM orders GROUP BY user_id HAVING SUM(amount) > 500; /* HAVING filters groups, WHERE filters rows */ /* Common aggregate functions */ COUNT(*) -- row count COUNT(col) -- non-null count SUM(col) AVG(col) MIN(col) MAX(col) STRING_AGG(col, ', ') -- PostgreSQL: join strings
JOINs
JOINs combine rows from multiple tables. Understanding which type to use is one of the most important SQL skills.
/* INNER JOIN — only rows with matching keys in both tables */ SELECT u.name, o.amount FROM users u INNER JOIN orders o ON o.user_id = u.id; /* LEFT JOIN — all users, even those with no orders */ SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON o.user_id = u.id; /* RIGHT JOIN — all orders, even without a matching user */ SELECT u.name, o.amount FROM users u RIGHT JOIN orders o ON o.user_id = u.id; /* FULL OUTER JOIN — all rows from both tables */ SELECT u.name, o.amount FROM users u FULL OUTER JOIN orders o ON o.user_id = u.id;
Subqueries
/* Scalar subquery — returns one value */ SELECT name, (SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS order_count FROM users u; /* IN subquery */ SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000); /* EXISTS — often faster than IN for large tables */ SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
Window Functions
Window functions compute values across a set of rows related to the current row — without collapsing them into groups like GROUP BY.
/* ROW_NUMBER — unique sequential number per partition */ SELECT name, country, ROW_NUMBER() OVER (PARTITION BY country ORDER BY created_at) AS row_num FROM users; /* RANK and DENSE_RANK */ SELECT product, sales, RANK() OVER (ORDER BY sales DESC) AS rank FROM sales_data; /* Running total */ SELECT date, amount, SUM(amount) OVER (ORDER BY date) AS running_total FROM daily_sales; /* LAG / LEAD — access previous/next row value */ SELECT date, amount, LAG(amount) OVER (ORDER BY date) AS prev_day, LEAD(amount) OVER (ORDER BY date) AS next_day FROM daily_sales;
Modifying Data
/* INSERT */
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com'), ('Charlie', 'c@example.com');
/* UPDATE */
UPDATE users SET status = 'inactive' WHERE last_login < '2023-01-01';
/* DELETE */
DELETE FROM sessions WHERE expires_at < NOW();
/* UPSERT (PostgreSQL) */
INSERT INTO settings (user_id, theme)
VALUES (1, 'dark')
ON CONFLICT (user_id) DO UPDATE SET theme = EXCLUDED.theme;Indexes
Indexes speed up reads at the cost of slower writes and more disk space. Always index foreign keys and columns used in WHERE clauses on large tables.
CREATE INDEX idx_users_email ON users (email); CREATE UNIQUE INDEX idx_users_email_unique ON users (email); CREATE INDEX idx_orders_user_created ON orders (user_id, created_at); /* composite */ /* See which index a query uses */ EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 1;
CTEs (WITH Clauses)
Common Table Expressions make complex queries readable by letting you name intermediate results.
WITH active_users AS ( SELECT id, name FROM users WHERE status = 'active' ), user_totals AS ( SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id ) SELECT u.name, ut.total FROM active_users u JOIN user_totals ut ON ut.user_id = u.id ORDER BY ut.total DESC;
Use the JSON Tools on io9.me to format query results that come back as JSON from your database driver.