The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Select columns | SELECT id, name FROM customers; | View examples |
| Rename an output column | SELECT total AS order_total FROM orders; | View examples |
| Remove duplicate rows | SELECT DISTINCT country FROM customers; | View examples |
| Filter by comparison | SELECT * FROM orders WHERE total >= 100; | View examples |
| Require every condition | SELECT *
FROM orders
WHERE status = 'paid' AND total >= 100; | View examples |
| Accept either condition | SELECT *
FROM orders
WHERE status = 'new' OR status = 'processing'; | View examples |
| Match a value list | SELECT *
FROM orders
WHERE status IN ('new', 'processing'); | View examples |
| Match an inclusive range | SELECT * FROM orders WHERE total BETWEEN 50 AND 100; | View examples |
| Find missing values | SELECT * FROM customers WHERE phone IS NULL; | View examples |
| Match a text pattern | SELECT * FROM customers WHERE name LIKE 'A%'; | View examples |
| Sort the result | SELECT * FROM orders ORDER BY created_at DESC, id ASC; | View examples |
| Limit returned rows | SELECT *
FROM orders
ORDER BY created_at DESC
FETCH FIRST 10 ROWS ONLY; | View examples |
| Skip result rows | SELECT *
FROM orders
ORDER BY id
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY; | View examples |
| Count rows | SELECT COUNT(*) AS order_count FROM orders; | View examples |
| Sum a column | SELECT SUM(total) AS revenue
FROM orders
WHERE status = 'paid'; | View examples |
| Aggregate by group | SELECT status, COUNT(*) FROM orders GROUP BY status; | View examples |
| Filter grouped results | SELECT customer_id, SUM(total)
FROM orders
GROUP BY customer_id
HAVING SUM(total) >= 500; | View examples |
| Join matching rows | SELECT o.id, c.name
FROM orders AS o
INNER JOIN customers AS c
ON c.id = o.customer_id; | View examples |
| Keep every left row | SELECT c.name, o.id
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.id; | View examples |
| Test for related rows | SELECT *
FROM customers AS c
WHERE EXISTS (
SELECT 1
FROM orders AS o
WHERE o.customer_id = c.id
); | View examples |
| Name an intermediate query | WITH paid_orders AS (
SELECT *
FROM orders
WHERE status = 'paid'
)
SELECT *
FROM paid_orders; | View examples |
Build SELECT queries in stages: choose source tables, filter rows, group when needed, calculate the output columns, and finally sort or limit the result. Explicit columns and deterministic ordering make queries easier to understand and safer to maintain.
Step by step
Detailed examples
Choose and name result columns
List the columns the caller actually needs instead of relying on SELECT *. Aliases make calculated fields easier to consume, while DISTINCT removes duplicate combinations only after the selected expressions have been calculated.
SELECT DISTINCT
country AS customer_country,
city AS customer_city
FROM customers
ORDER BY customer_country, customer_city; Note: DISTINCT applies to the complete selected row, so two cities with the same name in different countries remain separate combinations.
SELECT
id,
subtotal,
tax,
subtotal + tax AS order_total
FROM orders; Filter rows with explicit conditions
WHERE removes rows before grouping and output calculation. Combine conditions with parentheses when AND and OR appear together, use IS NULL instead of equality for missing values, and remember that BETWEEN includes both boundaries.
SELECT id, customer_id, status, total
FROM orders
WHERE status IN ('new', 'processing')
AND total BETWEEN 50 AND 100
ORDER BY id; SELECT id, name, email, phone
FROM customers
WHERE (name LIKE 'A%' OR name LIKE 'B%')
AND (email IS NULL OR phone IS NULL); Note: LIKE case sensitivity varies by database and collation. PostgreSQL also provides ILIKE for case-insensitive matching, but ILIKE is not portable SQL.
SELECT id, total
FROM orders
WHERE total >= 100; Sort before limiting or paging
A database does not guarantee row order without ORDER BY. Add a unique tie-breaker for stable results, then apply FETCH and OFFSET. Large offsets can become slow or unstable while data changes, so keyset pagination is often better for deep application pages.
SELECT id, customer_id, created_at, total
FROM orders
ORDER BY created_at DESC, id DESC
FETCH FIRST 10 ROWS ONLY; SELECT id, customer_id, created_at, total
FROM orders
ORDER BY id
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY; Note: FETCH is standard SQL and supported by PostgreSQL. Some databases use LIMIT, TOP, or different pagination syntax.
Summarize rows with aggregate functions
Aggregate functions calculate one value from many rows. GROUP BY produces one result row per key combination, WHERE filters source rows before aggregation, and HAVING filters the completed groups afterward.
SELECT
COUNT(*) AS order_count,
SUM(total) AS paid_revenue
FROM orders
WHERE status = 'paid'; Note: COUNT(*) counts rows. COUNT(column) counts only rows where that column is not NULL.
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total) AS paid_total
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
HAVING SUM(total) >= 500
ORDER BY paid_total DESC; SELECT status, COUNT(*) AS order_count
FROM orders
GROUP BY status
ORDER BY status; Join related tables with explicit keys
INNER JOIN returns only matching pairs. LEFT JOIN preserves every row from its left input and uses NULL for missing right-side values. Keep relationship predicates in ON so the intended join behavior remains visible.
SELECT
o.id AS order_id,
c.name AS customer_name,
o.total
FROM orders AS o
INNER JOIN customers AS c
ON c.id = o.customer_id
ORDER BY o.id; SELECT
c.id AS customer_id,
c.name,
COUNT(o.id) AS order_count
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name; Note: COUNT(o.id) returns zero for customers without orders, whereas COUNT(*) would count the preserved customer row.
Compose queries with EXISTS and CTEs
EXISTS expresses a Boolean relationship test and can stop after finding a qualifying row. A common table expression introduced by WITH gives an intermediate result a name, which can make multi-stage queries easier to review.
SELECT c.id, c.name
FROM customers AS c
WHERE EXISTS (
SELECT 1
FROM orders AS o
WHERE o.customer_id = c.id
AND o.status = 'paid'
)
ORDER BY c.name; WITH paid_orders AS (
SELECT customer_id, total
FROM orders
WHERE status = 'paid'
)
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total) AS paid_total
FROM paid_orders
GROUP BY customer_id
ORDER BY paid_total DESC; Note: A CTE improves structure but is not automatically faster than an equivalent subquery. Inspect the database execution plan for performance decisions.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



