Level 9: Linking two tables with JOIN
Updated on
Intermediate · level 9 of 20. Goal: Combine information from two tables linked by an id.
Topics: JOIN … ON table alias foreign key
Lesson
In a well-designed database, you avoid repeating information. The movies table does not contain the director's name, only director_id, a number. The name is stored once, in the directors table. If a director changes name, you only fix it in one place.
director_id is a foreign key: it points to the id column of directors, its primary key, which identifies each director uniquely. Night Train has director_id = 1: its director is the row of directors whose id is 1, Nora Ellis.
JOIN rebuilds that link. FROM movies JOIN directors ON directors.id = movies.director_id pairs each movie with the matching director row. ON gives the matching condition. The result has the columns of both tables.
To write less, you give each table a short alias: FROM movies m JOIN directors d ON d.id = m.director_id. You then prefix the columns: m.title, d.name. The prefix becomes mandatory when a column exists in both tables (id, name…): otherwise SQL does not know which one you mean.
JOIN (or INNER JOIN) only keeps the rows that have a match on both sides. Deep Current, without a director, disappears; Sara Diaz, without a movie, too. The next level shows how to keep them.
After a join, you can filter, sort and group as on a single table. When you group, group on the id as well as the name (GROUP BY d.id, d.name): two directors can share the same name. Our data has none, but it is common in a real database: build the habit now.
Syntax
SELECT a.col, b.col
FROM table_a a
JOIN table_b b ON b.id = a.b_id;Worked example
SELECT m.title, d.name AS director
FROM movies m
JOIN directors d ON d.id = m.director_id;Each movie is paired with its director. Deep Current, whose director_id is empty, doesn't appear.
| id | title | genre | year | duration | rating | director_id |
|---|---|---|---|---|---|---|
| 1 | Night Train | Thriller | 2015 | 118 | 7.8 | 1 |
| 2 | Blue Harbor | Drama | 2018 | 102 | 7.1 | 2 |
| 3 | Paper Moon City | Comedy | 2012 | 95 | 6.4 | 5 |
| 4 | Silent Peak | Drama | 2020 | 131 | 8.2 | 3 |
| 5 | Last Signal | Sci-Fi | 2019 | 142 | 7.5 | 1 |
| 6 | Summer Keys | Comedy | 2016 | 88 | 5.9 | 4 |
| 7 | Iron Garden | Sci-Fi | 2021 | 125 | 8 | 3 |
| 8 | Dust and Gold | Western | 2014 | 110 | 6.8 | 2 |
| 9 | The Quiet Hour | Drama | 2022 | 97 | 7.4 | 5 |
| 10 | Deep Current | Thriller | 2017 | 105 | 6.9 | NULL |
| id | name | country |
|---|---|---|
| 1 | Nora Ellis | UK |
| 2 | Paulo Reis | Brazil |
| 3 | Kenji Mori | Japan |
| 4 | Anna Berg | Sweden |
| 5 | Luc Martin | France |
| 6 | Sara Diaz | Spain |
Example result
| title | director |
|---|---|
| Night Train | Nora Ellis |
| Blue Harbor | Paulo Reis |
| Paper Moon City | Luc Martin |
| Silent Peak | Kenji Mori |
| Last Signal | Nora Ellis |
| Summer Keys | Anna Berg |
| Iron Garden | Kenji Mori |
| Dust and Gold | Paulo Reis |
| The Quiet Hour | Luc Martin |
Key points
- ON links the foreign key to the id.
- Prefix the columns with their table's alias.
- JOIN keeps only the rows that match.
Common pitfalls
- Two tables often have a name column: without a prefix, SQL reports an ambiguous column.
- Forgetting ON: SQLite then pairs each row with every row of the other table, without any error.
- GROUP BY a.name would merge two namesakes: group on a.id, a.name.
SQLite specifics
SQLite (like MySQL) accepts a JOIN without ON and treats it as a Cartesian product. PostgreSQL, SQL Server and Oracle reject the query: in standard SQL, JOIN requires ON (or USING). When you really want every combination, write it explicitly with CROSS JOIN.
The level’s 5 exercises
- Show the title of each book and the name of its author.
- Show the name of each player and the name of their team.
- Show the hotel name, the type and the price of the rooms in hotels located in 'Paris'.
- Show the name of each artist and their number of songs.
- Show the airline, arrival city and price of the 3 most expensive flights to a country other than France.
In SpeedQL, every query is checked straight away: its result is compared with the expected one, then the query is run again on a hidden control database. Each exercise has written hints, to show only if you get stuck.
See also: the cheat sheet card · the SQL exercises with solutions on this topic
Do the level 9 exercises
Free, no sign-up: the lesson and the 5 exercises open right in your browser.
← Previous level: Filtering groups with HAVING · Next level: LEFT JOIN and multiple joins →