
📎 This article includes 2 downloadable practice files ↓
SQL (Structured Query Language) is how you ask questions of a database. It has been around since the 1970s and is still the most useful data skill after Excel: almost every business system — SAP HANA, Oracle, SQL Server, PostgreSQL, MySQL — speaks it. This guide takes you from nothing to writing real reporting queries. Every example can be run in the SQL Playground on this site.
In this article
- 1. Tables, rows and columns
- 2. SELECT: choosing columns
- 3. WHERE: filtering rows
- 4. ORDER BY and calculated columns
- 5. Aggregates and GROUP BY — SQL’s pivot table
- 6. JOIN — combining tables
- 7. NULL — the value that is not a value
- 8. CASE — IF for SQL
- 9. Subqueries and CTEs
- 10. Window functions: rankings and running totals
- 11. Changing data: INSERT, UPDATE, DELETE
- 12. Creating tables
- 13. The order SQL really runs in
- 14. A practice plan
1. Tables, rows and columns
A database table is like a very disciplined Excel sheet: every column has a name and a data type (number, text, date), and every row is one record. Our practice table sales has one row per sale:
| id | rep | region | product | qty | price | sale_date |
|---|---|---|---|---|---|---|
| 1 | Asha | North | Monitor | 4 | 11499 | 2025-02-03 |
Unlike Excel, you never “scroll” a table — you describe the result you want, and the database works out how to get it.
2. SELECT: choosing columns
SELECT rep, product, qty
FROM sales;
SELECT * returns every column — handy for exploring, but name the columns in real queries so they keep working when the table changes. Add LIMIT 10 (MySQL, PostgreSQL, SQLite) or TOP 10 (SQL Server) to peek at a few rows.
3. WHERE: filtering rows
SELECT * FROM sales
WHERE region = 'North'
AND qty >= 5
AND sale_date BETWEEN '2025-01-01' AND '2025-03-31';
| Operator | Example |
|---|---|
| = , <> , < , >= | qty > 10 |
| IN | region IN ('North', 'East') |
| BETWEEN | price BETWEEN 500 AND 2000 (inclusive) |
| LIKE | rep LIKE 'A%' (starts with A) |
| IS NULL | phone IS NULL (never = NULL) |
4. ORDER BY and calculated columns
SELECT id, rep, qty * price AS amount
FROM sales
ORDER BY amount DESC;
AS gives a calculated column a name. DESC sorts largest first.
5. Aggregates and GROUP BY — SQL’s pivot table
SELECT region,
COUNT(*) AS orders,
SUM(qty * price) AS revenue,
AVG(qty) AS avg_qty
FROM sales
GROUP BY region
ORDER BY revenue DESC;
Every column in SELECT must either be in GROUP BY or be inside an aggregate (COUNT, SUM, AVG, MIN, MAX). Filter groups with HAVING: HAVING SUM(qty*price) > 100000.
6. JOIN — combining tables
Real databases split data across tables to avoid repeating it. JOIN puts it back together — it is VLOOKUP for whole tables.
SELECT s.id, s.region, r.manager, s.qty * s.price AS amount
FROM sales s
LEFT JOIN regions r ON s.region = r.region;
| Join | Keeps |
|---|---|
| INNER JOIN | Only rows that match in both tables |
| LEFT JOIN | All rows from the left table; blanks (NULL) where no match |
| RIGHT / FULL JOIN | All rows from the right / both tables |
SELECT region, COUNT(*) FROM regions GROUP BY region HAVING COUNT(*) > 1.7. NULL — the value that is not a value
NULL means “unknown”. It is not zero and not an empty string, and any comparison with NULL is unknown too — so WHERE discount = NULL returns nothing. Use IS NULL, and replace NULLs with COALESCE(discount, 0).
8. CASE — IF for SQL
SELECT id, qty,
CASE WHEN qty >= 20 THEN 'Large'
WHEN qty >= 5 THEN 'Medium'
ELSE 'Small' END AS order_size
FROM sales;
9. Subqueries and CTEs
-- sales above the average sale
SELECT * FROM sales
WHERE qty * price > (SELECT AVG(qty * price) FROM sales);
-- the same idea, more readable with a CTE
WITH totals AS (
SELECT rep, SUM(qty * price) AS revenue
FROM sales GROUP BY rep
)
SELECT * FROM totals WHERE revenue > 50000 ORDER BY revenue DESC;
A CTE (WITH … AS) is a named temporary result — like a helper sheet that exists only for this query.
10. Window functions: rankings and running totals
SELECT rep, region, qty * price AS amount,
RANK() OVER (PARTITION BY region ORDER BY qty * price DESC) AS rank_in_region,
SUM(qty * price) OVER (ORDER BY sale_date) AS running_total
FROM sales;
Window functions calculate across related rows without collapsing them the way GROUP BY does — perfect for top-N per group and cumulative totals.
11. Changing data: INSERT, UPDATE, DELETE
INSERT INTO sales (id, rep, region, product, qty, price, sale_date)
VALUES (41, 'Neha', 'West', 'Hub', 3, 2199, '2025-06-30');
UPDATE sales SET price = 649 WHERE product = 'Mouse';
DELETE FROM sales WHERE qty = 0;
BEGIN … COMMIT / ROLLBACK) on real systems.12. Creating tables
CREATE TABLE regions (
region VARCHAR(20) PRIMARY KEY,
manager VARCHAR(50) NOT NULL
);
A primary key guarantees each row is unique. An index (CREATE INDEX idx_sales_region ON sales(region);) makes filters and joins on that column fast.
13. The order SQL really runs in
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT. That is why you cannot use a SELECT alias in WHERE, but you can in ORDER BY.
14. A practice plan
- Run every query above in the SQL Playground.
- Solve its six challenges without looking at the answers.
- Download the CSV files from SQL for Excel users and load them into DB Browser for SQLite.
- Recreate one of your own Excel reports in SQL.
Converting your own CSV files into tables? Use the CSV to SQL converter.
📎 Practice files for this article
- 🧾Sales table (CSV)Load into DB Browser for SQLite to run every query in the guide.⬇ CSV · 3 KB
- 🧾Regions table (CSV)For the JOIN examples.⬇ CSV · 70 B
Free to use for learning. Files with macros (.bas) are plain text — import them with Alt+F11 → File → Import File, and always test on a copy.