SQL cheat sheet (PostgreSQL)
The statements you need most, one example each. Examples run on the bakery practice database and use PostgreSQL syntax. Other databases differ in small details, which are noted where they matter.
Read and filter
SELECT chooses columns, FROM names the table, WHERE keeps matching rows, ORDER BY sorts, LIMIT cuts the result. Text goes in single quotes.
SELECT name, unit_price FROM products WHERE category = 'pastry' AND unit_price < 3 ORDER BY unit_price DESC LIMIT 5;
Result: The pastries under 3.00, most expensive first.
Patterns, lists, ranges and NULL
LIKE matches patterns (% is any text, _ is one character; ILIKE ignores case). IN tests a list, BETWEEN tests an inclusive range, IS NULL finds missing values.
SELECT first_name, loyalty_tier FROM customers WHERE first_name LIKE 'A%' OR loyalty_tier IS NULL ORDER BY first_name;
Result: Customers whose name starts with A, or who have no loyalty tier.
Summarise with GROUP BY
COUNT, SUM, AVG, MIN and MAX turn rows into one value per group. WHERE filters rows before grouping, HAVING filters the groups.
SELECT channel, COUNT(*) AS orders FROM orders GROUP BY channel HAVING COUNT(*) > 5 ORDER BY channel;
Result: Only the online channel, with 7 orders.
Join tables
INNER JOIN keeps matches, LEFT JOIN keeps every row of the left table. Give tables short aliases and always join on the key.
SELECT p.name, SUM(oi.quantity) AS units FROM products AS p JOIN order_items AS oi ON oi.product_id = p.product_id GROUP BY p.name ORDER BY units DESC, p.name LIMIT 3;
Result: The three best-selling products.
CASE: if/else in SQL
CASE returns the value of the first condition that is true, like IFS in Excel. Without ELSE it returns NULL.
SELECT name,
CASE WHEN unit_price < 3 THEN 'budget' WHEN unit_price <= 4 THEN 'standard' ELSE 'premium' END AS band
FROM products
ORDER BY product_id;Result: Each product labelled budget, standard or premium.
Subqueries and WITH
A subquery is a query inside another. WITH names one so you can read the main query like a story.
WITH spend AS ( SELECT o.customer_id, SUM(oi.quantity * p.unit_price) AS total FROM orders AS o JOIN order_items AS oi ON oi.order_id = o.order_id JOIN products AS p ON p.product_id = oi.product_id GROUP BY o.customer_id ) SELECT customer_id, total FROM spend WHERE total > 30 ORDER BY total DESC;
Result: Customer 1 (Lopez) with 37.10 and customer 3 (Dubois) with 36.00.
Window functions
OVER adds a calculation across rows without collapsing them: ranks, running totals, and LAG/LEAD for the previous and next row.
SELECT order_id, order_date,
RANK() OVER (ORDER BY order_date) AS day_rank,
LAG(order_date) OVER (ORDER BY order_id) AS prev_date
FROM orders
ORDER BY order_id;Result: Each order with its date rank and the date of the order before.
Change data
INSERT adds rows, UPDATE changes them, DELETE removes them. Always include WHERE on UPDATE and DELETE, and run it as a SELECT first to see what it would touch. Wrap risky changes in BEGIN and ROLLBACK.
UPDATE products SET unit_price = unit_price + 0.10 WHERE category = 'drinks' RETURNING name, unit_price;
Result: The two drinks with their new prices.
Common questions
- In which order does SQL run a query?
- FROM and JOIN, then WHERE, GROUP BY, HAVING, SELECT, ORDER BY and finally LIMIT. That is why a WHERE cannot use a column alias from SELECT, but ORDER BY can.
- Is SQL case-sensitive?
- Keywords and table or column names are not (PostgreSQL folds unquoted names to lower case). Text values are: 'Paris' and 'paris' differ, unless you use ILIKE or LOWER().
- Does this work in MySQL, SQL Server or SQLite?
- The core (SELECT, WHERE, GROUP BY, JOIN, CASE) does. Differences are in LIMIT (TOP in SQL Server), string concatenation, date functions and RETURNING. Check your database's documentation for those.