SQL JOIN Explained with Visual Diagrams: Finally Understand How Tables Connect

PN
Priya Nair
Database Engineer & SQL Instructor | 9+ Years Experience

For nearly a year, I copy-pasted JOIN syntax from Stack Overflow without genuinely understanding what was happening underneath.

I could write a query that worked. I could not have told you, with confidence, why changing INNER JOIN to LEFT JOIN sometimes added rows, sometimes removed them, and sometimes changed nothing at all. I was pattern-matching syntax rather than understanding a concept, and it caught up with me constantly in code reviews when a senior engineer would ask why I chose a particular JOIN type and I genuinely could not explain my reasoning.

The breakthrough came from a colleague who stopped explaining JOIN syntax to me entirely and instead drew two overlapping circles on a whiteboard. That single picture did more for my understanding than months of reading documentation. This tutorial is built around that same visual approach.


The Problem With Most JOIN Explanations

Most SQL tutorials introduce JOINs by jumping straight into syntax: select columns, from table one, join table two, on some matching condition. This teaches you the grammar of a JOIN statement without teaching you what a JOIN conceptually does to your data.

The result is exactly what happened to me: people who can type JOIN syntax that compiles and runs, without an internal mental model for predicting what the output will actually contain before they run it.

Building that mental model is the entire goal of this tutorial, and the visual metaphor that finally worked for me, and has worked for every student I have since taught it to, is thinking about two tables as two overlapping circles.


The Two-Circle Mental Model

Imagine you have two tables. One table lists customers. Another table lists orders, where each order has a customer ID linking it back to a specific customer.

Picture this as two circles that partially overlap, like a Venn diagram. The left circle represents your customers table. The right circle represents your orders table. The overlapping middle section represents customers who have actually placed an order — the customer ID exists in both tables simultaneously.

Some customers might exist who have never placed an order — they fall in the left circle only, outside the overlap. Some orders might theoretically reference a customer ID that does not exist in your customers table at all (perhaps due to a data integrity issue) — these would fall in the right circle only, outside the overlap.

Every JOIN type is simply a different way of selecting which parts of this diagram you want to keep in your final result.


INNER JOIN: Only the Overlap

INNER JOIN keeps only the overlapping middle section of our diagram — rows where the matching value exists in both tables simultaneously.

In our customers and orders example, an INNER JOIN between these two tables returns only customers who have actually placed at least one order, combined with their order details. Any customer with zero orders disappears entirely from the result, since they fall outside the overlap. Any order somehow referencing a non-existent customer also disappears, for the same reason.

This is the JOIN type most tutorials teach first, and it makes sense as a starting point because it is the most intuitive: you are asking for records that genuinely correspond to each other in both tables.

The basic structure looks like this: you select your desired columns, specify your first table, then write JOIN followed by your second table name, then specify ON followed by the condition that defines how the two tables connect — typically matching a customer ID column in one table to the same customer ID column in the other.

Writing simply JOIN without any qualifying word in most database systems defaults to an INNER JOIN, which is worth knowing since you will see both INNER JOIN and plain JOIN used interchangeably in real code and documentation.


LEFT JOIN: Everything From the Left Circle, Plus Any Overlap

LEFT JOIN keeps everything from your first (left) table, regardless of whether a match exists in the second table, plus the overlapping matched data where it does exist.

In our example, a LEFT JOIN from customers to orders returns every single customer — including those who have never placed an order — combined with their order details where they exist. For customers with no orders, the order-related columns in the result simply show as empty (NULL) rather than that customer disappearing from the results entirely.

This distinction — INNER JOIN potentially losing rows that have no match, LEFT JOIN preserving every row from the left table regardless of match — is the single most important difference to internalize, because it answers a question that comes up constantly in real business analysis: “show me all customers, including ones who have not ordered anything yet.”

The structure is nearly identical to INNER JOIN, just replacing the word JOIN (or INNER JOIN) with LEFT JOIN, while keeping the same ON condition syntax.

A genuinely common mistake: People write a LEFT JOIN expecting to keep all customers, and then add a WHERE condition filtering on a column from the orders table — for example, filtering for orders placed after a specific date. This WHERE condition, applied after the LEFT JOIN, effectively eliminates the customers with no orders again, since their order date column is empty and fails the date comparison, defeating the entire purpose of using LEFT JOIN in the first place. If you need to filter the orders table while still preserving all customers, the filtering condition typically needs to be moved into the ON clause itself, or applied differently, depending on the exact business question — this nuance trips up even experienced SQL writers and is worth testing carefully whenever you combine LEFT JOIN with filtering conditions.


RIGHT JOIN: Everything From the Right Circle, Plus Any Overlap

RIGHT JOIN is the mirror image of LEFT JOIN: it keeps everything from your second (right) table, regardless of whether a match exists in the first table, plus the overlapping matched data.

Using our example again, a RIGHT JOIN from customers to orders would keep every order, including (hypothetically) any order that somehow references a customer ID not present in the customers table, while customers with no corresponding orders would simply not appear (since this version preserves the right table’s rows, not the left table’s).

In practice, RIGHT JOIN is used far less often than LEFT JOIN, simply because most people structure their query so the table they want to fully preserve is written first, and then use LEFT JOIN, rather than writing the table second and reaching for RIGHT JOIN. Functionally, swapping the order of two tables and switching between LEFT JOIN and RIGHT JOIN produces equivalent results, which is why many SQL writers default to always using LEFT JOIN and simply reordering their tables as needed, rather than keeping both LEFT and RIGHT JOIN in active daily use.


FULL OUTER JOIN: Everything From Both Circles

FULL OUTER JOIN keeps everything — the entire left circle, the entire right circle, and the overlapping middle, with no rows excluded from either side.

In our example, this would return every customer regardless of whether they have orders, every order regardless of whether its customer ID matches an actual customer record, and the properly matched combination where overlap exists. Any row without a corresponding match on the other side shows empty values for that other table’s columns, similar to how LEFT JOIN handles non-matching rows, but applied symmetrically to both tables rather than just one.

FULL OUTER JOIN is the JOIN type I see used least often in typical business reporting, since most business questions are naturally phrased from one table’s perspective (“show me all customers” or “show me all orders”) rather than genuinely needing the complete combined picture from both sides simultaneously. It becomes more relevant in specific data reconciliation scenarios — for example, comparing two systems that should theoretically have matching records, and specifically wanting to surface any mismatches from either direction.

One practical note: not every database system supports FULL OUTER JOIN directly (MySQL notably did not support it natively for a long time, though recent versions have added support, and workarounds combining a LEFT JOIN and RIGHT JOIN with a UNION have historically been used to simulate the same effect in systems lacking native support).


A Side-by-Side Comparison

Picture our customers table with three customers: Customer A has two orders, Customer B has one order, Customer C has zero orders. Our orders table has three orders, all correctly linked to either Customer A or Customer B.

INNER JOIN result: Three rows total — the two orders for Customer A, and the one order for Customer B. Customer C does not appear at all, since they have no matching order.

LEFT JOIN (customers to orders) result: Four rows total — the same three matched order rows, plus one additional row for Customer C, showing their customer details with empty order-related columns since no match exists.

RIGHT JOIN (customers to orders) result: Three rows total, identical to the INNER JOIN result in this specific scenario, since every order in our example does have a valid matching customer (there is no order with a non-existent customer ID to demonstrate the difference here).

FULL OUTER JOIN result: Four rows total, identical to the LEFT JOIN result in this specific scenario, since there is no orphaned order without a matching customer to demonstrate the additional difference FULL OUTER JOIN would show in a more complex example.

This comparison illustrates something important: in many everyday scenarios where your data is reasonably clean (every order has a valid customer), several JOIN types produce identical results, and the differences only become visible when you actually have the specific kind of unmatched data each JOIN type is designed to handle differently.


Practicing the Mental Model

Whenever you are deciding which JOIN type to use, ask yourself this specific question: do I need to potentially lose rows that have no match (suggesting INNER JOIN), or do I need to guarantee every row from one specific table appears regardless of whether a match exists (suggesting LEFT or RIGHT JOIN, depending on which table), or do I need absolutely everything from both tables regardless of matching (suggesting FULL OUTER JOIN)?

Translating a business question into this framework before writing any syntax is the habit that eventually replaced my old approach of copy-pasting JOIN patterns and hoping they worked. “Show me customers and their orders” suggests INNER JOIN if you only care about customers who have ordered something. “Show me all customers and whatever orders they might have” suggests LEFT JOIN. “Show me every order, and flag any that reference a customer we cannot find” suggests RIGHT JOIN or, more naturally phrased, a LEFT JOIN with the tables in the other order.


A Note on Multiple JOINs

Real queries frequently join more than two tables together — perhaps customers, orders, and a third table containing product details for each order. Each additional JOIN follows the exact same two-circle logic, just applied sequentially: first combine customers and orders according to whichever JOIN type fits that relationship, then take that combined result and JOIN it again with the products table according to whichever JOIN type fits that next relationship.

Thinking through multi-table JOINs one relationship at a time, rather than trying to visualize three or four overlapping circles simultaneously, keeps the same mental model manageable even as query complexity grows.


What Changed Once This Clicked

After that whiteboard explanation, JOIN syntax stopped being something I memorized and started being something I could reason through from the business question itself. I could look at a query someone else had written, identify the JOIN type, and predict roughly what shape of result it would produce, before ever running it against real data.

That predictive ability — knowing what you expect before you see the actual output — is the real marker of understanding JOINs rather than just being able to type the syntax. If you take nothing else from this tutorial, take the two-circle picture: it is worth more than memorizing any specific syntax pattern.

Which two tables are you trying to join, and what is the specific business question you are trying to answer? Describe your situation and I can tell you exactly which JOIN type fits and walk through what the result will contain.

About the Author

Priya Nair is a database engineer and SQL instructor with 9 years of experience teaching SQL to bootcamp students and corporate teams. She has taught over 2,000 students from complete beginners to working analysts.