Complete SQL Training Guide for Beginners to Experts
What This Guide Covers
This is a complete SQL training resource. It goes from absolute basics to advanced queries you'll actually use on the job. No academic fluff. No theory that never applies. Just SQL that works.
SQL is the language for talking to databases. Every app, every website, every analytics dashboard relies on it. If you touch data, you need SQL. Period.
Getting Started: Your First Query
Before writing queries, you need somewhere to run them. Options:
- SQLite — comes with Python, zero setup, fine for learning
- PostgreSQL — free, production-grade, what most companies use
- MySQL — popular with web apps, owned by Oracle
- DB Browser for SQLite — GUI tool, easiest for beginners
- dbeaver — free database tool that connects to almost anything
Download one. Install it. Create a test database. That's your playground for the rest of this guide.
The Absolute Basics
Every SQL query follows the same pattern. Start here:
SELECT column_name FROM table_name;
This pulls data from a table. That's it. That's the foundation.
Want multiple columns?
SELECT column1, column2, column3 FROM table_name;
Want everything?
SELECT * FROM table_name;
The asterisk means "all columns." Use it when exploring. Avoid it in production code — selecting only what you need is faster and clearer.
Filtering Data
Pulling entire tables is useless. You need to filter. That's what the WHERE clause does.
SELECT name, email FROM users WHERE age >= 18;
This pulls name and email from users, but only rows where age is 18 or higher.
Comparison Operators
- = equals
- != or <> not equal
- > greater than
- < less than
- >= greater than or equal
- <= less than or equal
Combining Conditions
Need multiple filters? Use AND and OR.
SELECT * FROM orders
WHERE status = 'shipped' AND total > 100;
OR gives you more matches:
SELECT * FROM users
WHERE country = 'USA' OR country = 'Canada';
Watch your logic. AND runs before OR. Use parentheses to control the order:
SELECT * FROM users
WHERE (country = 'USA' OR country = 'Canada') AND age > 25;
IN and BETWEEN
Instead of chaining ORs, use IN:
SELECT * FROM products WHERE category IN ('electronics', 'books', 'clothing');
BETWEEN grabs a range:
SELECT * FROM sales WHERE amount BETWEEN 50 AND 200;
Pattern Matching with LIKE
Need to match text patterns? LIKE does that.
SELECT * FROM users WHERE email LIKE '%@gmail.com';
The % is a wildcard. It matches anything before @gmail.com.
- % matches any sequence of characters
- _ matches exactly one character
SELECT * FROM products WHERE name LIKE 'iPhone _';
This matches "iPhone 12", "iPhone 13", etc.
Sorting and Limiting Results
ORDER BY sorts your results:
SELECT name, price FROM products ORDER BY price DESC;
DESC means descending (highest first). Omit it or use ASC for ascending (lowest first).
LIMIT restricts how many rows come back:
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;
This gets the 10 most recent users. Common pattern. Remember it.
Aggregation Functions
Aggregate functions crunch numbers across rows. The big four:
- COUNT() — counts rows
- SUM() — adds values
- AVG() — calculates average
- MIN()/MAX() — finds extremes
SELECT COUNT(*) FROM orders WHERE status = 'completed';
SELECT AVG(price) FROM products WHERE category = 'electronics';
GROUP BY
Aggregates alone give you one number. GROUP BY breaks it down by category:
SELECT category, COUNT(*) as product_count, AVG(price) as avg_price
FROM products
GROUP BY category;
This tells you how many products and the average price per category. The AS keyword creates an alias — a nickname for the column in your results.
HAVING
WHERE filters rows before grouping. HAVING filters groups after:
SELECT category, COUNT(*) as count
FROM products
GROUP BY category
HAVING count > 5;
This only shows categories with more than 5 products.
Quick note: WHERE comes before GROUP BY. HAVING comes after. Don't mix them up.
Joins: Combining Tables
Real data lives across multiple tables. Joins let you pull it together.
INNER JOIN
Returns rows that have matches in both tables:
SELECT orders.id, users.name, orders.total
FROM orders
INNER JOIN users ON orders.user_id = users.id;
This pulls order details with customer names. The ON clause specifies how the tables connect.
LEFT JOIN
Returns all rows from the left table, plus matches from the right. Non-matching right rows get NULL:
SELECT users.name, orders.id
FROM users
LEFT JOIN orders ON users.id = orders.user_id;
This shows every user, even those who never placed an order. Their order columns will be empty.
RIGHT JOIN and FULL OUTER JOIN
Right join is the reverse of left join. Full outer join returns everything from both tables. Not every database supports them. PostgreSQL does. MySQL doesn't.
Multiple Joins
Chain joins to connect more tables:
SELECT orders.id, users.name, products.title
FROM orders
INNER JOIN users ON orders.user_id = users.id
INNER JOIN order_items ON orders.id = order_items.order_id
INNER JOIN products ON order_items.product_id = products.id;
Three tables connected in one query. This is normal. Get comfortable with it.
Subqueries
A subquery is a query inside another query. Use them when you can't get the answer in one step.
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
This finds employees earning above average. The inner query runs first, then the outer query uses that result.
Subqueries work in WHERE, FROM, and SELECT clauses. They can be correlated (reference the outer query) or uncorrelated (standalone). Correlated subqueries are slower. Sometimes a JOIN is faster. Test both.
Modifying Data
SQL isn't just for reading. You can insert, update, and delete.
INSERT
INSERT INTO users (name, email, age) VALUES ('John Doe', 'john@example.com', 28);
Always specify columns. It's safer and easier to read.
UPDATE
UPDATE users SET age = 29 WHERE id = 5;
Always include a WHERE clause. Forget it, and you update every row in the table. That's a disaster. Trust me.
DELETE
DELETE FROM users WHERE id = 5;
Same rule. WHERE clause or you wipe the table. In production, wrap destructive queries in transactions until you're sure they work.
Creating Tables
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Common constraints:
- PRIMARY KEY — unique identifier for each row
- NOT NULL — column cannot be empty
- UNIQUE — no duplicate values allowed
- DEFAULT — value if none provided
- FOREIGN KEY — references another table
Data types vary by database. SQLite has INTEGER, TEXT, REAL, BLOB. PostgreSQL and MySQL have more options: VARCHAR, BOOLEAN, TIMESTAMP, JSON, etc.
Common Mistakes Beginners Make
- Forgetting WHERE on UPDATE/DELETE — test with SELECT first, then swap in your modifier
- Using OR when IN would work — IN is cleaner and sometimes faster
- Selecting * in production — name your columns explicitly
- Ignoring NULLs — NULL is not zero, not empty string. It means "unknown." Use IS NULL or IS NOT NULL to check it
- Not indexing — slow queries on big tables? Probably missing an index
- Forgetting to commit — in databases with transaction support, changes aren't permanent until you COMMIT
SQL Tools Compared
Tool Best For Cost Learning Curve
SQLite Local development, small apps, learning Free Low
PostgreSQL Production apps, complex data, analytics Free Medium
MySQL Web apps, WordPress, LAMP stack Free/Premium Low
SQL Server Windows environments, enterprise Paid Medium
BigQuery Massive datasets, cloud analytics Pay per query Medium
DB Browser Visual SQL work, beginners Free Very Low
DBeaver Multi-database management Free/Premium Low
Pick based on your goal. Learning SQL syntax? SQLite. Job in data? PostgreSQL. Building web apps? MySQL or PostgreSQL.
Practice Problems to Build Skill
Reading this guide isn't enough. You need to write queries. Here are problems that actually test your skills:
- Find the top 5 users by total order value
- List products that have never been ordered (LEFT JOIN with NULL check)
- Calculate month-over-month revenue growth
- Find duplicate email addresses in the users table
- Rank customers by purchase frequency
Struggling? That's normal. Break the problem down. What tables do you need? How do they connect? What filter gets you close? Build the query piece by piece.
Where to Practice
- SQLZoo — interactive exercises, browser-based
- LeetCode — SQL practice problems, common in interviews
- Mode Analytics SQL Tutorial — free, good explanations
- HackerRank — SQL track with difficulty levels
- Your own database — import a dataset and explore it
What's Next
You know the fundamentals. Here's what separates intermediate from advanced SQL:
- Window functions (ROW_NUMBER, RANK, LAG, LEAD)
- Common Table Expressions (CTEs with WITH)
- Complex joins and self-joins
- Query optimization and EXPLAIN plans
- Database-specific features (PostgreSQL's JSON operations, MySQL's full-text search)
Pick one. Learn it deeply. Build something with it. That's how you actually learn SQL.