← Back to courses

Course

SQL & Database Fundamentals

177 lessons across 12 modules

Beginner to advanced, with no prerequisites. The concepts are database-independent and the exercises run on PostgreSQL: table design, filtering, aggregation, joins, subqueries and CTEs, changing data safely, constraints, window functions, indexing and EXPLAIN, and transactions - ending in a learning platform database built from requirements. The road is laid out in full; lessons are being written one at a time.

Course12 modules177 lessonsEach heading below is a module (one topic). Each card under it is a lesson. Start with Module 1.
Solid: ready (0)Dashed: coming soon (177)
Module 1 of 1212 lessonsComing soon

Database and SQL Fundamentals

What a database is, and a first query against one

What is a Database?

1

Organised, persistent data that many people can safely use at once.

Two people editing the same record, and what should happen

Lesson 1planned

Database vs Spreadsheet

2

Where a spreadsheet stops - concurrency, integrity, and scale.

A shared sheet with three versions of the same customer

Lesson 2planned

Relational Databases

3

Data in tables, connected by the values they share.

Customers and orders linked by an id

Lesson 3planned

What is SQL?

4

A declarative language - you say what you want, not how to get it.

One query replacing a loop over every row

Lesson 4planned

SQL vs NoSQL

5

Relational against document, key-value, and graph - an honest comparison.

The same data in a table and in a document

Lesson 5planned

Tables, Rows and Columns

6

Columns are the shape, rows are the facts.

A users table read row by row

Lesson 6planned

Primary Keys

7

The idea - one value that identifies exactly one row.

Two customers named John, told apart by their key

Lesson 7planned

Foreign Keys

8

The idea - a column that points at a row in another table.

An order that knows which customer placed it

Lesson 8planned

Relationships

9

One-to-one, one-to-many, and many-to-many.

Students and courses joined through enrollments

Lesson 9planned

PostgreSQL Setup

10

Installing PostgreSQL locally or running it in a container.

A database running in one command

Lesson 10planned

SQL Clients

11

psql and graphical clients, and when each is better.

The same query run in the terminal and in a GUI

Lesson 11planned

Your First SQL Query

12

Writing, running, and reading the result of a query.

Listing every row in a table, then just one

Lesson 12planned
Module 2 of 1218 lessonsComing soon

Database Design and Tables

Creating tables, and choosing the right type for each column

Creating a Database

13

A database as a container for tables, and naming it.

A learning database created and connected to

Lesson 13planned

Creating Tables

14

Designing columns before writing any SQL.

A users table sketched on paper first

Lesson 14planned

SQL Data Types

15

Why the type matters - storage, validation, and sorting.

Dates stored as text sorting in the wrong order

Lesson 15planned

INTEGER

16

Whole numbers, their ranges, and BIGINT for ids.

An id column that ran out of numbers

Lesson 16planned

VARCHAR

17

Text with a maximum length, and whether the limit is worth having.

A name truncated by a limit that was too small

Lesson 17planned

TEXT

18

Unlimited text - and in PostgreSQL, no slower than VARCHAR.

Choosing between TEXT and VARCHAR for an email

Lesson 18planned

BOOLEAN

19

True, false, and the third value, NULL.

An is_active flag that is neither true nor false

Lesson 19planned

DATE

20

A calendar day with no time and no time zone.

A birthday that should not shift across time zones

Lesson 20planned

TIMESTAMP

21

Date and time - and why TIMESTAMPTZ is usually the right one.

A created_at that is wrong for every user abroad

Lesson 21planned

NUMERIC

22

Exact decimals, and never storing money as a float.

0.1 + 0.2 in a float column against NUMERIC

Lesson 22planned

UUID

23

Globally unique ids, and their trade-offs against integers.

Ids that can be generated before the row is saved

Lesson 23planned

CREATE TABLE

24

The full statement, column by column.

The users table from the sketch, written out

Lesson 24planned

Primary Key

25

Declaring a primary key when the table is created.

An id column with PRIMARY KEY and a generated value

Lesson 25planned

Foreign Key

26

Declaring a foreign key with REFERENCES.

An orders table that references users

Lesson 26planned

NOT NULL

27

Declaring that a column must always have a value.

An email that can never be missing

Lesson 27planned

UNIQUE

28

Declaring that no two rows may share a value.

Two accounts with the same email, prevented

Lesson 28planned

DEFAULT

29

A value used when an insert does not supply one.

created_at filled in automatically

Lesson 29planned

CHECK

30

A rule every row must satisfy.

An age that cannot be negative

Lesson 30planned
Module 3 of 1218 lessonsComing soon

SELECT and Filtering

Asking for exactly the rows and columns you want

SELECT

31

The statement you will write more than any other.

Reading from a table for the first time

Lesson 31planned

Selecting Specific Columns

32

Naming the columns you need, and only those.

Name and email, not the whole row

Lesson 32planned

SELECT *

33

Every column - fine for exploring, risky in application code.

A new column breaking code that used SELECT *

Lesson 33planned

Column Aliases

34

Renaming a column in the result with AS.

A computed column given a readable name

Lesson 34planned

DISTINCT

35

Removing duplicate rows from a result.

Every city customers live in, each listed once

Lesson 35planned

WHERE

36

Keeping only the rows that match a condition.

Only the users who signed up this year

Lesson 36planned

Comparison Operators

37

Equals, not equals, greater and less than.

Orders over a threshold

Lesson 37planned

AND

38

Requiring several conditions at once.

Adults who are also active

Lesson 38planned

OR

39

Accepting either condition - and the brackets it needs beside AND.

A missing bracket returning far too many rows

Lesson 39planned

NOT

40

Negating a condition, and how NULL complicates it.

A NOT that silently drops rows with NULLs

Lesson 40planned

IN

41

Matching any value in a list.

Three statuses in one condition instead of three ORs

Lesson 41planned

BETWEEN

42

An inclusive range - and the trap with timestamps.

A date range that misses the whole last day

Lesson 42planned

LIKE

43

Pattern matching with % and _.

Every email at one domain

Lesson 43planned

ILIKE

44

Case-insensitive matching, specific to PostgreSQL.

A search that finds John, john, and JOHN

Lesson 44planned

IS NULL

45

Testing for a missing value - and why = NULL never works.

A WHERE email = NULL that returns nothing

Lesson 45planned

IS NOT NULL

46

Keeping only rows that have a value.

Users who have added a phone number

Lesson 46planned

CASE

47

Conditional values inside a query.

Scores turned into grades in the result

Lesson 47planned

COALESCE

48

The first value that is not NULL.

A display name falling back to the email

Lesson 48planned
Module 4 of 1214 lessonsComing soon

Sorting, Grouping and Aggregation

Ordering results, and summarising many rows into few

ORDER BY

49

Sorting a result - and results having no order without it.

A query that happened to be sorted until it was not

Lesson 49planned

Ascending vs Descending

50

ASC, DESC, sorting on several columns, and where NULLs go.

Newest first, then alphabetical for ties

Lesson 50planned

LIMIT

51

Returning only the first rows of a sorted result.

The top ten highest-scoring students

Lesson 51planned

OFFSET

52

Skipping rows - the syntax behind page two, three, and four.

Page three of a list, twenty rows per page

Lesson 52planned

COUNT

53

COUNT(*) against COUNT(column), and what NULL does to each.

Two counts on the same table that disagree

Lesson 53planned

SUM

54

Adding a column up across rows.

Total revenue for the month

Lesson 54planned

AVG

55

The mean, and NULLs being skipped rather than counted as zero.

An average that ignored the students with no score

Lesson 55planned

MIN

56

The smallest value - numbers, dates, or text.

The earliest signup date

Lesson 56planned

MAX

57

The largest value.

The most recent order per customer, as a first attempt

Lesson 57planned

GROUP BY

58

One result row per group, instead of one per input row.

Employee count per department

Lesson 58planned

HAVING

59

Filtering groups after they have been aggregated.

Departments with more than five employees

Lesson 59planned

Aggregate Functions

60

How aggregates treat NULLs and empty groups, all together.

SUM of no rows returning NULL, not zero

Lesson 60planned

Grouping Multiple Columns

61

A group for every distinct combination of values.

Revenue per region per month

Lesson 61planned

Filtering Aggregated Data

62

WHERE before grouping against HAVING after - and choosing between them.

The same filter in both places, and different answers

Lesson 62planned
Module 5 of 1212 lessonsComing soon

Joins

Combining tables through the values they share

Why Joins?

63

Data split across tables for good reasons, and put back together.

An order list that needs customer names beside it

Lesson 63planned

Primary Key and Foreign Key Relationships

64

How a join walks from a foreign key to the row it points at.

orders.user_id matched to users.id, drawn out

Lesson 64planned

INNER JOIN

65

Only the rows that match on both sides.

Users who have placed at least one order

Lesson 65planned

LEFT JOIN

66

Every row on the left, matched where possible, NULL where not.

Every user, including those with no orders

Lesson 66planned

RIGHT JOIN

67

The mirror of LEFT JOIN - and why most people rewrite it as one.

The same query as a RIGHT and as a LEFT join

Lesson 67planned

FULL OUTER JOIN

68

Every row from both sides, matched or not.

Reconciling two lists to find what is missing from each

Lesson 68planned

CROSS JOIN

69

Every combination of rows - occasionally useful, often an accident.

Every size paired with every colour

Lesson 69planned

Self Join

70

Joining a table to itself.

Each employee listed beside their manager

Lesson 70planned

Multiple Joins

71

Chaining joins, and the order they are read in.

Users, their orders, and the products in each order

Lesson 71planned

Joining Multiple Tables

72

Many-to-many through a join table.

Students and courses joined through enrollments

Lesson 72planned

Join Conditions

73

ON against WHERE, and where a filter belongs in an outer join.

A filter in WHERE that turned a LEFT JOIN into an INNER one

Lesson 73planned

Common Join Mistakes

74

Missing conditions, fan-out, and duplicated totals.

A revenue figure doubled by joining through the wrong table

Lesson 74planned
Module 6 of 1212 lessonsComing soon

Subqueries and CTEs

Queries inside queries, and naming the steps

What is a Subquery?

75

A query used as a value, a list, or a table inside another.

Users who spent more than the average

Lesson 75planned

Scalar Subqueries

76

A subquery returning exactly one value - and failing if it returns two.

The latest order date shown on every row

Lesson 76planned

Subqueries with WHERE

77

Filtering against a computed list or value.

Products that have never been ordered

Lesson 77planned

Subqueries with FROM

78

A subquery acting as a temporary table.

Aggregating, then filtering the aggregate

Lesson 78planned

Correlated Subqueries

79

A subquery that refers to the outer row - and runs once per row.

Each customer and their own most recent order

Lesson 79planned

EXISTS

80

True as soon as one matching row is found.

Users who have at least one completed enrollment

Lesson 80planned

NOT EXISTS

81

The safe anti-join - and why NOT IN breaks on NULLs.

A NOT IN returning nothing because of one NULL

Lesson 81planned

Common Table Expressions

82

Naming the intermediate steps of a query.

A four-level nested query rewritten as three named steps

Lesson 82planned

WITH

83

The syntax, and reading a CTE top to bottom.

An active_users step used by the final select

Lesson 83planned

Multiple CTEs

84

Several steps, each building on the one before.

Filter, aggregate, then rank, as three named stages

Lesson 84planned

Recursive CTEs

85

Walking a hierarchy of unknown depth.

Every lesson under a course, however deeply nested

Lesson 85planned

CTE vs Subquery

86

Readability, reuse, and what the planner does with each.

The same query both ways, and the plan for each

Lesson 86planned
Module 7 of 1212 lessonsComing soon

INSERT, UPDATE and DELETE

Changing data, safely

INSERT

87

Adding rows, and naming the columns you are filling.

An insert that breaks when a column is added, and one that does not

Lesson 87planned

Single Row Insert

88

One row, and getting its generated id back with RETURNING.

Inserting a user and using the new id straight away

Lesson 88planned

Multiple Row Insert

89

Many rows in one statement, and why it is faster.

A thousand rows in one statement against a thousand statements

Lesson 89planned

INSERT ... SELECT

90

Inserting the result of a query.

Copying last month rows into an archive table

Lesson 90planned

UPDATE

91

Changing values in existing rows.

Marking one order as shipped

Lesson 91planned

Updating Multiple Rows

92

Updating by condition, and updating from another table.

Applying a price change to a whole category

Lesson 92planned

DELETE

93

Removing rows.

Deleting one expired session

Lesson 93planned

Conditional Delete

94

Deleting by condition - and checking the condition first.

Running the WHERE as a SELECT before the DELETE

Lesson 94planned

TRUNCATE

95

Emptying a table fast, and what it skips that DELETE does not.

TRUNCATE against DELETE on a million-row table

Lesson 95planned

Upsert

96

Insert if new, update if it exists - in one statement.

Recording progress whether or not a row exists yet

Lesson 96planned

PostgreSQL ON CONFLICT

97

DO NOTHING against DO UPDATE, and the excluded row.

An upsert that keeps the higher of two scores

Lesson 97planned

Safe Data Modification

98

Transactions, RETURNING, and never running a bare UPDATE.

An UPDATE with no WHERE, and how to survive one

Lesson 98planned
Module 8 of 1212 lessonsComing soon

Constraints and Data Integrity

Making the database refuse bad data

Primary Keys

99

Choosing a key - surrogate against natural, and composite keys.

An email used as a primary key, and the day it had to change

Lesson 99planned

Foreign Keys

100

What the database enforces, and what it costs on writes.

An order for a user who does not exist, refused

Lesson 100planned

Unique Constraints

101

Adding uniqueness to a table that already has duplicates.

Finding and fixing duplicates before the constraint will apply

Lesson 101planned

Not Null

102

Making an existing column required.

Backfilling NULLs before adding the constraint

Lesson 102planned

Check Constraints

103

Business rules the database enforces, named so errors are readable.

An end date that must come after the start date

Lesson 103planned

Default Values

104

Changing a default, and what happens to rows already stored.

A new default that does not rewrite old rows

Lesson 104planned

Referential Integrity

105

No reference ever points at a row that is not there.

Orphaned rows in a database without foreign keys

Lesson 105planned

Cascade

106

Letting one change ripple through related rows - deliberately.

Deleting a course and its lessons going with it

Lesson 106planned

ON DELETE

107

CASCADE, SET NULL, RESTRICT - choosing per relationship.

Deleting a user without deleting their invoices

Lesson 107planned

ON UPDATE

108

Propagating a changed key, and why stable keys avoid the need.

A renamed code updating every row that uses it

Lesson 108planned

Data Validation

109

What to validate in the application and what in the database.

A rule enforced in both places, and why

Lesson 109planned

Database-Level Integrity

110

The database as the last line of defence for every client.

A second service writing data the first would have rejected

Lesson 110planned
Module 9 of 1215 lessonsComing soon

Advanced SQL

Window functions, conditional aggregates, and richer types

Window Functions

111

Calculating across related rows without collapsing them into groups.

Each salary shown beside its department average

Lesson 111planned

ROW_NUMBER

112

A unique sequence number within each partition.

The latest order per customer, done properly

Lesson 112planned

RANK

113

Ranking with gaps after ties.

Two students tied for second, and nobody in third

Lesson 113planned

DENSE_RANK

114

Ranking without gaps.

The same tie, with the next student placed third

Lesson 114planned

LEAD

115

Reading a value from the next row.

The time until each user next logged in

Lesson 115planned

LAG

116

Reading a value from the previous row.

Month-over-month revenue change

Lesson 116planned

Running Totals

117

Cumulative sums with a window frame.

Revenue to date, day by day

Lesson 117planned

Partitioning Data with OVER

118

PARTITION BY, ORDER BY, and frames inside OVER.

A running total that restarts each month

Lesson 118planned

Conditional Aggregation

119

Aggregating only some rows with CASE inside the aggregate.

Completed and abandoned counts in one row

Lesson 119planned

FILTER

120

The cleaner syntax for the same thing.

The previous query rewritten with FILTER

Lesson 120planned

STRING_AGG

121

Joining many values into one string, in order.

Every tag on a course as one comma-separated list

Lesson 121planned

Date Functions

122

Truncating, extracting, and date arithmetic.

Signups grouped by week

Lesson 122planned

String Functions

123

Case, trimming, splitting, and concatenation.

Normalising emails before comparing them

Lesson 123planned

JSON Operations

124

Reading from and querying JSON columns.

Filtering rows by a value inside a JSON document

Lesson 124planned

PostgreSQL Arrays

125

Array columns, and when a separate table is the better choice.

Tags as an array against tags as their own table

Lesson 125planned
Module 10 of 1216 lessonsComing soon

Indexing and Query Performance

Making queries fast, and proving that they are

What is an Index?

126

A separate sorted structure that lets the database skip most rows.

A book index against reading every page

Lesson 126planned

Why Indexes?

127

Faster reads, paid for with slower writes and more storage.

A lookup going from seconds to milliseconds

Lesson 127planned

B-Tree Index

128

The default index, and which operators it can serve.

Why an index helps = and < but not a leading-wildcard LIKE

Lesson 128planned

Composite Index

129

Several columns, and why their order decides what it can serve.

An index on (a, b) helping a query on a but not on b

Lesson 129planned

Unique Index

130

An index that also enforces uniqueness.

The index created behind every UNIQUE constraint

Lesson 130planned

Partial Index

131

Indexing only the rows you actually query.

An index over active users only, a tenth of the size

Lesson 131planned

Expression Index

132

Indexing a computed value.

An index on lower(email) for case-insensitive lookups

Lesson 132planned

Index Selectivity

133

Why an index on a column with three values rarely gets used.

An index on status that the planner ignores

Lesson 133planned

When NOT to Create an Index

134

Small tables, write-heavy tables, and indexes nothing uses.

Finding and dropping unused indexes

Lesson 134planned

EXPLAIN

135

The plan the database intends to use.

Reading a plan from the innermost node outwards

Lesson 135planned

EXPLAIN ANALYZE

136

Running the query and comparing estimates with reality.

A row estimate off by a factor of a thousand

Lesson 136planned

Sequential Scan

137

Reading every row - and when that is genuinely the right choice.

A seq scan beating an index on a small table

Lesson 137planned

Index Scan

138

Index scans, index-only scans, and bitmap scans.

A covering index answering a query without the table

Lesson 138planned

Query Optimization

139

Measure, change one thing, and measure again.

A slow report fixed with one index and one rewrite

Lesson 139planned

N+1 Query Problem

140

What it looks like from the database side, and fixing it with a join.

A hundred and one queries in the log for one page

Lesson 140planned

Pagination Performance

141

Why deep OFFSET pages slow down, and keyset pagination instead.

Page 5000 taking seconds, then milliseconds

Lesson 141planned
Module 11 of 1218 lessonsComing soon

Transactions and Database Programming

Correctness under concurrency, and logic inside the database

What is a Transaction?

142

Several statements that succeed or fail as one.

A transfer that debits but never credits

Lesson 142planned

BEGIN

143

Opening a transaction, and autocommit when you do not.

Every statement committed on its own by default

Lesson 143planned

COMMIT

144

Making the changes permanent and visible to others.

A change invisible to another session until commit

Lesson 144planned

ROLLBACK

145

Abandoning every change since BEGIN, and savepoints.

Rolling back half a transaction to a savepoint

Lesson 145planned

ACID

146

The four guarantees a transaction makes.

Each guarantee matched to the failure it prevents

Lesson 146planned

Atomicity

147

All or nothing, even across a crash.

Power lost halfway through, and nothing half-applied

Lesson 147planned

Consistency

148

Every transaction leaving the constraints satisfied.

A transaction rejected for breaking a foreign key

Lesson 148planned

Isolation

149

Concurrent transactions not seeing each other half-finished.

Two sessions reading the same row mid-change

Lesson 149planned

Durability

150

Committed means it survives a crash.

The write-ahead log, in one diagram

Lesson 150planned

Isolation Levels

151

Read committed, repeatable read, serializable - and their anomalies.

A lost update at read committed, prevented at serializable

Lesson 151planned

Deadlocks

152

Two transactions each waiting for the other, and avoiding it.

Locking rows in a consistent order

Lesson 152planned

Locks

153

Why the database locks, and what waits on what.

A migration blocked by one long-running query

Lesson 153planned

Row-Level Locks

154

SELECT FOR UPDATE, and SKIP LOCKED for job queues.

Two workers never picking up the same job

Lesson 154planned

Stored Procedures

155

Logic run inside the database, with transaction control.

A procedure that archives and deletes in one call

Lesson 155planned

Functions

156

Reusable SQL that returns a value or a table.

A function computing course completion percentage

Lesson 156planned

Triggers

157

Code that runs automatically on a change - used sparingly.

An updated_at column that maintains itself

Lesson 157planned

Views

158

A saved query that behaves like a table.

A view that hides sensitive columns from reports

Lesson 158planned

Materialized Views

159

A stored result, refreshed on your schedule.

An expensive dashboard query computed hourly

Lesson 159planned
Module 12 of 1218 lessonsComing soon

Real-World SQL Project

A learning platform database, from requirements to review

Build the API on top: the Node.js course →

Analyze Requirements

160

Turning a product description into questions the data must answer.

A list of reports the platform will need

Lesson 160planned

Design Entities

161

Tenants, users, roles, courses, modules, lessons, and enrollments.

Deciding what is an entity and what is an attribute

Lesson 161planned

Create ER Diagram

162

Drawing entities and relationships before any SQL.

The whole schema on one page

Lesson 162planned

Create Database

163

The project database, and its roles and permissions.

An application role that cannot drop tables

Lesson 163planned

Create Tables

164

Every table, with types chosen deliberately.

A migration file per table

Lesson 164planned

Add Relationships

165

Foreign keys, including the many-to-many join tables.

Enrollments linking users to courses

Lesson 165planned

Add Constraints

166

Every rule the business relies on, enforced by the database.

A progress value that must stay between 0 and 100

Lesson 166planned

Insert Sample Data

167

Realistic seed data, in volume, generated rather than typed.

Ten thousand enrollments from generate_series

Lesson 167planned

Write Basic Queries

168

The everyday reads the application needs.

A learner dashboard listing their courses

Lesson 168planned

Write Join Queries

169

Queries spanning the course hierarchy.

Every lesson a learner has not yet completed

Lesson 169planned

Build Reporting Queries

170

Aggregates the business actually asks for.

Completion rate per course per tenant

Lesson 170planned

Write CTE Queries

171

Multi-step reports broken into named stages.

A funnel from enrollment to completion

Lesson 171planned

Write Window Function Queries

172

Rankings and trends across the data.

Top learners per course, and weekly growth

Lesson 172planned

Add Indexes

173

Indexes chosen from the queries you actually wrote.

One composite index serving three queries

Lesson 173planned

Analyze Queries

174

EXPLAIN ANALYZE on every important query.

A plan table for the whole project

Lesson 174planned

Optimize Slow Queries

175

Fixing the worst offender, and measuring the result.

The slowest report made fifty times faster

Lesson 175planned

Implement Transactions

176

The operations that must be atomic, done correctly.

Enrolling in a course and creating its progress rows together

Lesson 176planned

Final Database Review

177

The schema reviewed against the original requirements.

The finished database, and what you would change next time

Lesson 177planned