SQL joins combine data from multiple tables using a related column. In real-world systems, data is normalized and spread across many tables โ employees in one table, departments in another, locations in a third, sales facts in a fourth surrounded by dimension tables. Joins are what let you reconnect that data and ask questions that span across it, like โwhich employees work in which locations, under which managers, and how does that compare to a full org reconciliation ahead of a system migration?โ
This is the module where SQL stops being about one table and starts being about your entire database โ and where correctness alone stops being enough, because the same logically correct join can run in milliseconds or minutes depending on how itโs written and indexed.
Every analytics dashboard, every report, every API endpoint backed by a relational database is, underneath, a chain of joins. A data model normalized into clean, single-purpose tables is only useful if you can reliably and efficiently put it back together โ thatโs the entire discipline this module teaches.
| No. | Topic | Description | Files |
|---|---|---|---|
| 01 | INNER JOIN | Return only matching rows from both tables | .md ยท .sql |
| 02 | LEFT JOIN | Return all rows from the left table, matched or not | .md ยท .sql |
| 03 | RIGHT JOIN | Return all rows from the right table, matched or not | .md ยท .sql |
| 04 | FULL OUTER JOIN | Return all rows from both tables, matched or not | .md ยท .sql |
| 05 | CROSS JOIN | Every row of one table paired with every row of the other | .md ยท .sql |
| No. | Topic | Description | Files |
|---|---|---|---|
| 06 | SELF JOIN | Join a table to itself โ hierarchies, org charts | .md ยท .sql |
| 07 | MULTI-TABLE JOINS โญ | Chain 3+ tables; semi/anti joins; the NOT IN NULL trap |
.md ยท .sql |
| 08 | JOIN PERFORMANCE ๐ | EXPLAIN, indexing, join algorithms, star schemas |
.md ยท .sql |
| 09 | BUSINESS CASES โญ | Capstone: real multi-domain scenarios end to end | .md ยท .sql |
Each .md file explains the concept, execution flow, algorithms, vendor differences, and reasoning; the paired .sql file contains runnable, multi-question, annotated examples against the shared schema.
03_Joins/
โ
โโโ README.md
โโโ ENGINEERING_AUDIT_REPORT.md
โโโ CONTRIBUTOR_CHECKLIST.md
โ
โโโ schema/
โ โโโ 00_schema_setup.sql โ run this first โ canonical CREATE TABLE + seed data
โ
โโโ 01_INNER_JOIN.md 01_INNER_JOIN.sql
โโโ 02_LEFT_JOIN.md 02_LEFT_JOIN.sql
โโโ 03_RIGHT_JOIN.md 03_RIGHT_JOIN.sql
โโโ 04_FULL_OUTER_JOIN.md 04_FULL_OUTER_JOIN.sql
โโโ 05_CROSS_JOIN.md 05_CROSS_JOIN.sql
โโโ 06_SELF_JOIN.md 06_SELF_JOIN.sql
โโโ 07_MULTI_TABLE_JOINS.md 07_MULTI_TABLE_JOINS.sql
โโโ 08_JOIN_PERFORMANCE.md 08_JOIN_PERFORMANCE.sql
โโโ 09_BUSINESS_CASES.md 09_BUSINESS_CASES.sql
โ
โโโ assets/
โโโ diagrams/
โโโ inner-join.svg
โโโ left-join.svg
โโโ right-join.svg
โโโ full-outer-join.svg
โโโ cross-join.svg
โโโ self-join.svg
โโโ multi-table-chain.svg
โโโ join-execution-order.svg
โโโ join-algorithms.svg
โโโ star-schema.svg
๐ Every topic file (
01โ09) embeds its own diagram inline โ youโll see the relevant Venn diagram, hierarchy tree, join chain, or schema diagram right at the top of each fileโs Concept Overview, not just here in the README.
1. INNER JOIN โ only the rows that match in both tables
2. LEFT JOIN โ everything on the left, matched or not
3. RIGHT JOIN โ everything on the right, matched or not
4. FULL OUTER JOIN โ both of those, combined โ plus the MySQL UNION workaround
5. CROSS JOIN โ every pairing, no predicate โ date spines & combinatorics
6. SELF JOIN โ a table joined to itself (hierarchies)
7. MULTI-TABLE JOINS โ chaining everything above across 3+ tables, plus semi/anti joins
8. JOIN PERFORMANCE โ EXPLAIN, indexing, join algorithms, star schemas
9. BUSINESS CASES โ capstone: real multi-domain scenarios end to end
๐ก
04_FULL_OUTER_JOINis deliberately placed right afterLEFT/RIGHTโ itโs easiest to understand as โboth of those, combined,โ and grouping the four core join types together before branching into composition (self joins, multi-table chains) keeps the mental model tight.
Run schema/00_schema_setup.sql once before working through any topic file. Every query in this module runs against this exact schema โ itโs the single source of truth for table structure and seed data.
locations (1) โโโโโโ< departments (1) โโโโโโ< employees
โ
โ manager_id (self-FK)
โโโโโโโโ
locations โ offices; departments โ linked via location_id (nullable โ some departments are remote-first)employees โ linked to departments via dept_id (nullable โ some employees are unassigned)employees.manager_id โ self-referencing FK to employees.emp_id (used in 06_SELF_JOIN)09_BUSINESS_CASES.sql additionally introduces a small, self-contained e-commerce star schema for its dimensional-modeling scenario โ it does not modify the core HR schema above.One connected schema across eight of the nine topics means youโre learning join logic, not re-learning a new dataset every lesson โ and the ninth topic deliberately switches domains once, on purpose, to test whether that logic actually transfers.
Every join answers the same underlying question: for each row in table A, what row(s) in table B share a value that makes the ON predicate true?
| Join Type | Keeps unmatched rows fromโฆ |
|---|---|
INNER JOIN |
Neither table โ only matches survive |
LEFT JOIN |
The left table |
RIGHT JOIN |
The right table |
FULL OUTER JOIN |
Both tables |
CROSS JOIN |
N/A โ no predicate; every pairing is kept, matched or not |
SELF JOIN |
Same rules as INNER/LEFT/RIGHT โ itโs a role, not a distinct join type |
| Multi-table | Depends on which join type chains each pair |
๐ Key mental model: unmatched rows from the โkeptโ side appear with
NULLin every column that comes from the other table. This is the #1 source of confusion when debugging join results โ always check whatNULLis telling you, and see NULL Handling below.
Every clause in a join query is also evaluated in a specific logical order โ understanding it is what explains why filtering an outer-joined table in WHERE behaves differently from filtering it in ON:
The SQL you write is a request, not an execution plan โ the engine chooses one of three physical strategies:
| Algorithm | Best when |
|---|---|
| Nested Loop | One side is small, or thereโs a usable index on the join key |
| Hash Join | Large, unsorted tables, equality predicate, no useful index |
| Merge Join | Both sides already sorted (often via an index) on the join key |
Full treatment, including how to confirm which one actually ran, is in 08_JOIN_PERFORMANCE.md.
NULL never equals anything โ not a value, not another NULL. This single fact explains almost every join surprise in this module:
dept_id = NULL never matches any department in an INNER JOIN, even hypothetically against another NULL.NOT IN (SELECT nullable_column FROM ...) silently returns zero rows if that column contains even one NULL โ covered in depth in 07_MULTI_TABLE_JOINS.md.NOT NULL column in its own table can still show NULL in a joinโs result set, when it comes from the unmatched side of an outer join.EXPLAIN output and reason about index usage and join algorithm choice| Use Case | Example Question Answered |
|---|---|
| Employee reporting | Which department and location does each employee belong to? |
| Data quality audits | Which employees or departments are missing an expected relationship? |
| Workforce planning | Which departments are funded but currently unstaffed? |
| Migration reconciliation | Which records in either system have no counterpart in the other? |
| Analytical / BI reporting | Monthly revenue by category and country, from a star-schema fact table |
| Manager hierarchy analysis | Who reports to whom, and how does compensation compare within teams? |
Joins are among the most frequently asked SQL interview topics, and this module is deliberately weighted toward the sub-topics that most reliably separate strong candidates: the ON-vs-WHERE placement trap for outer joins, the NOT IN NULL trap for anti joins, the MySQL FULL OUTER JOIN gap, and the ability to read an EXPLAIN plan and name the join algorithm that ran. Interviewers frequently test this by asking you to predict row counts before running a query, or to spot whatโs silently wrong with a query that looks correct.
LEFT/RIGHT/INNER โ donโt guessNOT EXISTS (or LEFT JOIN ... IS NULL) for anti joins โ never NOT IN against a possibly-nullable columnEXPLAIN/EXPLAIN ANALYZE on any join touching a non-trivial table before shipping itWHERE instead of ON, silently collapsing an outer join into an inner joinNOT IN against a subquery column that can contain NULLFULL OUTER JOIN works in MySQL without the UNION emulationCompletion of 01_Fundamentals and 02_Aggregations. Youโll frequently combine joins with GROUP BY, HAVING, window functions, and CTEs โ especially in 08_JOIN_PERFORMANCE and 09_BUSINESS_CASES.
# 1. Clone the handbook (if you haven't already)
git clone https://github.com/theammarngp-makes/SQL-Engineering-Handbook.git
cd SQL-Engineering-Handbook/03_Joins
# 2. Spin up a scratch PostgreSQL database (or point psql at an existing one)
createdb sql_joins_practice
psql -d sql_joins_practice -f schema/00_schema_setup.sql
# 3. Work through the topics in order, running each .sql file as you go
psql -d sql_joins_practice -f 01_INNER_JOIN.sql
MySQL users:
schema/00_schema_setup.sqlis ANSI-compatible and runs unmodified on MySQL 8.0+ โ load it withmysql your_db < schema/00_schema_setup.sql. Watch for the MySQL-specific call-outs in04_FULL_OUTER_JOIN.mdand08_JOIN_PERFORMANCE.md.
schema/00_schema_setup.sql once, against a scratch database..md file for a topic to understand the concept, execution flow, algorithms, and vendor differences..sql fileโs queries one at a time, checking the โEXPECTED OUTPUTโ comment against what you actually get.ON to WHERE (or vice versa) and compare row counts โ this is the fastest way to internalize the single most commonly misunderstood join behavior.07_MULTI_TABLE_JOINS and 09_BUSINESS_CASES, build each query incrementally: one join or one CTE layer at a time, confirming row counts before adding the next.โฑ Estimated time: 6โ8 hours for the lessons and examples, plus additional time for the practice challenges in each file.
Join fluency is a baseline expectation, not a differentiator, for Data Analyst, Data Engineer, Analytics Engineer, and BI Developer roles โ but the specific sub-skills this module emphasizes (outer join filter placement, NULL-safe anti joins, reading execution plans, recognizing star schemas) are exactly what separates a candidate who can write a join from one who can be trusted to write joins against production data without a senior engineer reviewing every query.
This module is held to a high bar deliberately โ see ENGINEERING_AUDIT_REPORT.md for the standard it was built against. Before opening a PR against any file here:
CONTRIBUTOR_CHECKLIST.md in full..sql file against a freshly-seeded database (schema/00_schema_setup.sql) and confirm every EXPECTED OUTPUT comment still matches reality..md file in this module rather than inventing a new shape.Found a bug, a stale comment, or a gap this audit missed? Issues and PRs are genuinely welcome โ thatโs the whole point of building this in the open.
โฌ
๏ธ 02_Aggregations
โก๏ธ 04_Subqueries โ nest queries inside other queries to answer multi-step business questions; several patterns in 09_BUSINESS_CASES.md (the CTE-based compensation query) are a direct preview.
ENGINEERING_AUDIT_REPORT.md โ the audit that produced this moduleโs current structure, for context on why itโs organized this way.| Mohammad Ammar โ Co-Founder @ Apex Analyticx, Data Analytics Engineer, author of the SQL Engineering Handbook (20+ modules). Based in Nagpur, India. |