Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

alias
A short second name given to a table or column in a query, so the rest of the query can refer to it briefly.
Cartesian product
Every row of one table paired with every row of another, which happens when a join has no matching rule.
column
One named field in a table, with the same kind of value in every row, such as a title or a year.
constraint
A rule a table enforces on its data, such as requiring a value or making one column unique.
distinct
A word in a query that removes repeated rows from the result, so each one appears once.
foreign key
A column that refers to the primary key of another table, so each row can be tied to one row there.
index
A stored lookup that helps the database find rows quickly without reading the whole table.
inner join
The default way of combining two tables: only pairs of rows present on both sides appear in the result.
join
A query operation that combines rows from two tables by matching values they share, usually a shared identifier.
junction table
A bridge between two tables that lets a row on either side pair with many on the other; each of its rows holds one pair.
left join
A combination that keeps every row of the first table named, filling in nulls where the second has no match.
many-to-many
A relationship where one row on each side can pair with many rows on the other, usually held in a junction table.
null
A marker for a value that is missing or unknown, which is not the same as zero or an empty string.
outer join
A join that keeps rows from one or both tables even when no row on the other side matches.
primary key
The column or columns that together identify each row uniquely, and which can never be missing.
query
A request written in SQL that asks the database for an answer, usually a table of rows.
row
One record in a table, with one value for each column that table defines.
schema
The stored plan of a database: its tables, the columns in each, and the rules those columns must follow.
table
A named set of rows that all share the same columns, such as one holding every book in a library.

SQL from the ground up

Joining tables

Two tables, one key: INNER JOIN keeps the rows that match on both sides, LEFT JOIN keeps every row on the left, and aliases keep the query readable.

Part 6 of 12

Intermediate17 min readBack to Courses

Two tables, one key

Data is split across tables on purpose: the authors table holds each person once, and every book points at its author with an id. A join puts the two back together for the length of one query, matching each book to the author row whose id it carries.

The matching condition goes in ON: JOIN authors ON authors.id = books.author_id. It says what makes a pair. Everything you know about filtering and ordering still applies to the joined result as if it were one table.

Aliases keep it readable

Once a query names two tables, every column reference must say which one it means. Short aliases are the habit that makes joins readable: FROM books b JOIN authors a ON a.id = b.author_id, and then b.title, a.name in the rest of the query.

The alias is also how the same table can be joined to itself later — twice, under two names — which is the trick behind any query about pairs or hierarchies.

INNER, and the rows that stayed out

A plain JOIN is an INNER JOIN: only pairs that match are kept. An author with no books simply disappears from the result — which is right when you asked about books, and wrong when you asked about authors.

LEFT JOIN keeps every row of the left table whatever happens, filling the right side with NULL where nothing matched. That is how "which authors have written nothing?" is asked: join, then look for the empty side with IS NULL.

What ON is doing

A join builds pairs. Conceptually the database takes every row of the left table with every row of the right — the Cartesian product — and keeps the pairs where the ON condition is true. That is why a missing or wrong ON produces a flood of rows: every pair passes.

Real databases do not build the whole product; they use an index or a hash table to find the matching rows directly. But the meaning is the product-and-filter, and that meaning is what tells you how many rows to expect.

A join does not merge the tables. It produces a new result table with the columns of both sides, which is why SELECT * on a join shows duplicate-looking id columns and why naming the columns you want is worth the typing.

One row can become many

If the right side has three rows for one left row, the join returns three rows. That is correct for a question about sales, and wrong for a count of books: COUNT(*) after joining books to sales counts sales, not books. COUNT(DISTINCT b.id) restores the unit you meant.

A many-to-many relationship cannot live in either table as a list. It needs a third table whose rows are pairs — one column for each side — and a query crosses it with two joins. The junction table is not red tape; it is the only shape that lets one book have many tags and one tag belong to many books.

Join order, and the one that is not symmetric

For inner joins, the order the tables are written in does not change the answer. A JOIN B and B JOIN A produce the same pairs, with the columns in a different order, and a chain of three can be joined in whichever sequence the database finds cheapest. Each ON is a statement about a pair of tables, and the result is every row that satisfies all of them at once.

A LEFT JOIN is the exception. It is not symmetric: the left table is the one kept whole, so swapping the two sides changes which rows survive. That is why an outer join is written in the order the question was asked — authors first when the report is about authors — rather than in whatever order reads neatly.

Books with their authors, and authors with no books

SELECT b.title, a.name AS author
FROM books b
JOIN authors a ON a.id = b.author_id
ORDER BY a.name, b.title;

SELECT a.name
FROM authors a
LEFT JOIN books b ON b.author_id = a.id
WHERE b.id IS NULL;

Three join mistakes: leaving out ON, so every row pairs with every row; counting rows after a one-to-many join without DISTINCT, which counts the children instead of the parents; and joining on a column that can be NULL, where the missing keys never equal each other and those rows quietly drop out.

Try these next

  • Add a second tag to one book and watch that book appear twice in the join.
  • Find every tag that no book uses with a LEFT JOIN and IS NULL.
  • Count the books per tag, including the tags with none.

Books and their authors

List every book with its author name, then find the authors who have no books at all.

Live Preview

Books and tags, through a junction

Run both queries. Then add a fourth book with two tags and find where it appears in each answer.

Live Preview

A condition on the right-hand table in the WHERE clause of a LEFT JOIN quietly turns it back into an inner join — rows that would have been NULL fail the test. Conditions about the outer join belong in the ON clause.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Which rows does an INNER JOIN return?