🎓 12 free lessons · ~10 hours total

SQL Tutorial — 12 lessons, all hands-on

Most online SQL tutorials are videos with a fake terminal. This one drops you into a real SQLite engine in your browser and asks you to write the query. From your first SELECT to top-N-per-group window functions in 12 steps.

Start Lesson 1 → See all 12 lessons ↓

No install · No signup · Take the placement check to skip what you know

Why this tutorial

Three things most SQL tutorials skip

1

You write queries in lesson 1

Not videos, not slides — a real SQLite engine in your browser. Lesson 1's first query runs in under 60 seconds.

2

Wrong-answer diagnostics

When your query fails, you get the specific gap — wrong column count, sort order, NULL bug — not a generic "incorrect."

3

Mastery checks & spaced retrieval

Lesson done isn't enough. The Coach schedules a fresh-solve check the next day so concepts transfer to long-term memory.

The Curriculum

12 lessons, in order

Click into the app to start any lesson. The placement check at the start of the app skips lessons you've already mastered.

01

Your first SELECT

Beginner⏱ 10 min

Read columns from a table. Understand SELECT, FROM, and the difference between picking specific columns and SELECT *. By the end you can list every passenger on the Titanic and how old they were.

SELECT name, age FROM passengers;
Start lesson 1
02

Filtering with WHERE

Beginner⏱ 20 min

Filter rows with predicates. Compare numbers (>, <, BETWEEN), match strings (LIKE, IN), and combine conditions with AND/OR. The trap most beginners hit: col = NULL never returns rows — use IS NULL.

SELECT name FROM passengers WHERE age BETWEEN 25 AND 35 AND class = '1st';
Start lesson 2
03

Sorting and limiting

Beginner⏱ 15 min

Order results with ORDER BY (ascending, descending, multiple columns), cut to top-N with LIMIT, paginate with OFFSET. The non-obvious rule: top-N without an ORDER BY is undefined.

SELECT name, age FROM passengers ORDER BY age DESC, name ASC LIMIT 10;
Start lesson 3
04

Aggregate functions

Beginner⏱ 20 min

COUNT, SUM, AVG, MIN, MAX. The subtlety: COUNT(*) counts all rows including NULLs; COUNT(col) ignores NULLs. AVG and SUM also ignore NULLs.

SELECT COUNT(*) AS total, AVG(age) AS avg_age, MAX(age) AS oldest FROM passengers;
Start lesson 4
05

GROUP BY and HAVING

Beginner⏱ 25 min

Compute aggregates per category with GROUP BY, filter groups with HAVING. Master the conditional-aggregate trick (SUM(CASE WHEN ...)) — it's the single most-asked technique in interview SQL.

SELECT class, COUNT(*) AS n, SUM(CASE WHEN survived = 1 THEN 1 ELSE 0 END) AS survivors FROM passengers GROUP BY class HAVING COUNT(*) > 100;
Start lesson 5
06

INNER JOIN — combining two tables

Intermediate⏱ 30 min

Join two tables with INNER JOIN on a key. Understand "row multiplication" (one-to-many joins multiply row counts) and how to alias tables for clarity. This is the lesson where most beginners fall behind in their first SQL course — give it real time.

SELECT o.id, c.name, o.amount FROM orders o INNER JOIN customers c ON c.id = o.customer_id WHERE o.amount > 100;
Start lesson 6
07

LEFT JOIN and anti-joins

Intermediate⏱ 30 min

LEFT JOIN keeps unmatched rows on the left. The anti-join pattern (LEFT JOIN ... WHERE right.id IS NULL) finds "X without a matching Y." The most expensive bug in SQL: filtering the right table in WHERE instead of ON silently turns LEFT into INNER.

-- Customers who never ordered SELECT c.name FROM customers c LEFT JOIN orders o ON o.customer_id = c.id WHERE o.id IS NULL;
Start lesson 7
08

Subqueries and EXISTS

Intermediate⏱ 25 min

Use a query as a value (scalar subquery), as a list (IN), or as a check (EXISTS). Learn why NOT IN is a NULL trap and NOT EXISTS is the safe alternative.

SELECT name, age, age - (SELECT AVG(age) FROM passengers) AS diff FROM passengers WHERE EXISTS ( SELECT 1 FROM tickets t WHERE t.passenger_id = passengers.id );
Start lesson 8
09

CTEs and recursive CTEs

Intermediate⏱ 30 min

Break complex queries into named steps with WITH. Each CTE reads top-to-bottom — far more maintainable than nested subqueries. Recursive CTEs walk hierarchies (org charts, comment trees) and generate sequences.

WITH top_customers AS ( SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY customer_id HAVING SUM(amount) > 10000 ) SELECT c.name, t.total FROM top_customers t JOIN customers c ON c.id = t.customer_id;
Start lesson 9
10

CASE WHEN — SQL's if-statement

Intermediate⏱ 25 min

Express conditional logic in SELECT, in ORDER BY, and inside aggregates. The pivot pattern — SUM(CASE WHEN x THEN 1 ELSE 0 END) — turns categorical data into separate counted columns. Foundation of every dashboard query.

SELECT class, SUM(CASE WHEN sex = 'M' THEN 1 ELSE 0 END) AS men, SUM(CASE WHEN sex = 'F' THEN 1 ELSE 0 END) AS women FROM passengers GROUP BY class;
Start lesson 10
11

Window function basics

Advanced⏱ 45 min

ROW_NUMBER, RANK, DENSE_RANK. Understand PARTITION BY (groups within a window) and how the three rankers handle ties differently. The single most-asked topic in mid-to-senior data interviews.

SELECT name, dept, salary, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk FROM employees;
Start lesson 11
12

Advanced windows — LAG/LEAD, running totals, top-N per group

Advanced⏱ 60 min

LAG and LEAD peek at neighboring rows for day-over-day deltas, churn detection, sessionization. Running totals and rolling averages with the ROWS BETWEEN frame. Top-N per group via ROW_NUMBER + filter — the single most-asked window pattern.

-- Day-over-day price change per stock SELECT ticker, day, price, price - LAG(price) OVER ( PARTITION BY ticker ORDER BY day ) AS daily_delta FROM prices;
Start lesson 12
After lesson 12

Where to go from here

12 lessons gets you fluent enough to write everyday analyst SQL. Pick the next track based on your goal.

Frequently Asked

Lesson 1 takes 10 minutes.
Open it now.

Free, browser-only, no signup. The placement check skips lessons you've already mastered.

Start the Tutorial — Free ⚡