What is a SQL JOIN?
A JOIN is used to combine rows from two or more tables based
on a related column. JOINs are one of the most important features of
relational databases because related information is often stored in
separate tables.
For example, a school database may store student information in one
table and examination results in another. A JOIN can combine these
tables using a common column such as student_id.
Basic JOIN Syntax
The basic JOIN syntax specifies the tables to combine and the columns that connect them.
CREATE TABLE players (
player_id INTEGER,
player_name TEXT,
team_id INTEGER
);
CREATE TABLE teams (
team_id INTEGER,
team_name TEXT
);
INSERT INTO players VALUES
(1, 'Leo', 10),
(2, 'Maya', 20),
(3, 'Noah', 10);
INSERT INTO teams VALUES
(10, 'Falcons'),
(20, 'Rockets');
SELECT
players.player_name,
teams.team_name
FROM players
JOIN teams
ON players.team_id = teams.team_id;
INNER JOIN
An INNER JOIN returns only rows where a matching value
exists in both tables.
CREATE TABLE airports (
airport_id INTEGER,
airport_name TEXT,
city TEXT
);
CREATE TABLE flights (
flight_id INTEGER,
airport_id INTEGER,
flight_code TEXT
);
INSERT INTO airports VALUES
(1, 'Skyport Airport', 'Delhi'),
(2, 'Harbor Airport', 'Mumbai'),
(3, 'Mountain Airport', 'Leh');
INSERT INTO flights VALUES
(101, 1, 'SK101'),
(102, 2, 'HB202'),
(103, 1, 'SK303');
SELECT
a.airport_name,
a.city,
f.flight_code
FROM airports AS a
INNER JOIN flights AS f
ON a.airport_id = f.airport_id;
INNER JOIN with WHERE
You can combine an INNER JOIN with a
WHERE clause to filter the joined results.
CREATE TABLE chefs (
chef_id INTEGER,
chef_name TEXT
);
CREATE TABLE dishes (
dish_id INTEGER,
chef_id INTEGER,
dish_name TEXT,
price INTEGER
);
INSERT INTO chefs VALUES
(1, 'Anika'),
(2, 'Kabir'),
(3, 'Rhea');
INSERT INTO dishes VALUES
(101, 1, 'Pasta', 450),
(102, 2, 'Curry', 380),
(103, 1, 'Pizza', 550),
(104, 3, 'Soup', 250);
SELECT
c.chef_name,
d.dish_name,
d.price
FROM chefs AS c
INNER JOIN dishes AS d
ON c.chef_id = d.chef_id
WHERE d.price > 400;
LEFT JOIN
A LEFT JOIN returns all rows from the left table and the
matching rows from the right table. If no match exists, the right-side
columns contain NULL.
CREATE TABLE authors (
author_id INTEGER,
author_name TEXT
);
CREATE TABLE articles (
article_id INTEGER,
author_id INTEGER,
title TEXT
);
INSERT INTO authors VALUES
(1, 'Nora'),
(2, 'Arman'),
(3, 'Ivy'),
(4, 'Sam');
INSERT INTO articles VALUES
(201, 1, 'The Solar System'),
(202, 3, 'Ocean Life'),
(203, 1, 'Future Cities');
SELECT
a.author_name,
ar.title
FROM authors AS a
LEFT JOIN articles AS ar
ON a.author_id = ar.author_id
ORDER BY a.author_id;
LEFT JOIN with NULL
A LEFT JOIN can be used with IS NULL to find records that
do not have a matching record in another table.
CREATE TABLE workshops (
workshop_id INTEGER,
workshop_name TEXT
);
CREATE TABLE registrations (
registration_id INTEGER,
workshop_id INTEGER,
participant TEXT
);
INSERT INTO workshops VALUES
(1, 'Photography'),
(2, 'Robotics'),
(3, 'Astronomy'),
(4, 'Drawing');
INSERT INTO registrations VALUES
(101, 1, 'Ravi'),
(102, 3, 'Meera'),
(103, 1, 'Zoya');
SELECT
w.workshop_name
FROM workshops AS w
LEFT JOIN registrations AS r
ON w.workshop_id = r.workshop_id
WHERE r.registration_id IS NULL;
JOIN Using Table Aliases
Table aliases provide short names for tables and make JOIN queries easier to read.
CREATE TABLE planets (
planet_id INTEGER,
planet_name TEXT
);
CREATE TABLE moons (
moon_id INTEGER,
planet_id INTEGER,
moon_name TEXT
);
INSERT INTO planets VALUES
(1, 'Earth'),
(2, 'Mars'),
(3, 'Jupiter');
INSERT INTO moons VALUES
(101, 1, 'Moon'),
(102, 2, 'Phobos'),
(103, 2, 'Deimos');
SELECT
p.planet_name,
m.moon_name
FROM planets AS p
INNER JOIN moons AS m
ON p.planet_id = m.planet_id;
JOIN with ORDER BY
After joining tables, ORDER BY can be used to arrange the
resulting rows.
CREATE TABLE games (
game_id INTEGER,
game_name TEXT
);
CREATE TABLE scores (
score_id INTEGER,
game_id INTEGER,
points INTEGER
);
INSERT INTO games VALUES
(1, 'Space Run'),
(2, 'Castle Quest'),
(3, 'Ocean Race');
INSERT INTO scores VALUES
(101, 1, 850),
(102, 2, 1200),
(103, 3, 950),
(104, 1, 1400);
SELECT
g.game_name,
s.points
FROM games AS g
INNER JOIN scores AS s
ON g.game_id = s.game_id
ORDER BY s.points DESC;
JOIN with GROUP BY
JOINs can be combined with GROUP BY and aggregate functions
to summarize related records.
CREATE TABLE stores (
store_id INTEGER,
store_name TEXT
);
CREATE TABLE sales (
sale_id INTEGER,
store_id INTEGER,
amount INTEGER
);
INSERT INTO stores VALUES
(1, 'Central Store'),
(2, 'North Store'),
(3, 'West Store');
INSERT INTO sales VALUES
(101, 1, 500),
(102, 1, 750),
(103, 2, 900),
(104, 2, 400),
(105, 3, 650);
SELECT
st.store_name,
SUM(s.amount) AS total_sales
FROM stores AS st
INNER JOIN sales AS s
ON st.store_id = s.store_id
GROUP BY st.store_id, st.store_name
ORDER BY total_sales DESC;
JOIN Three Tables
You can join three or more tables when the tables are connected through related columns.
CREATE TABLE customers (
customer_id INTEGER,
customer_name TEXT
);
CREATE TABLE orders (
order_id INTEGER,
customer_id INTEGER,
product_id INTEGER
);
CREATE TABLE products (
product_id INTEGER,
product_name TEXT
);
INSERT INTO customers VALUES
(1, 'Aarav'),
(2, 'Meera'),
(3, 'Tara');
INSERT INTO orders VALUES
(101, 1, 10),
(102, 2, 20),
(103, 1, 30);
INSERT INTO products VALUES
(10, 'Tablet Stand'),
(20, 'USB Hub'),
(30, 'Desk Light');
SELECT
c.customer_name,
p.product_name
FROM customers AS c
INNER JOIN orders AS o
ON c.customer_id = o.customer_id
INNER JOIN products AS p
ON o.product_id = p.product_id
ORDER BY c.customer_name;
SELF JOIN
A SELF JOIN joins a table with itself. It is useful when
rows within the same table have a relationship with each other.
CREATE TABLE staff (
staff_id INTEGER,
staff_name TEXT,
supervisor_id INTEGER
);
INSERT INTO staff VALUES
(1, 'Elena', NULL),
(2, 'Jon', 1),
(3, 'Priya', 1),
(4, 'Samir', 2);
SELECT
e.staff_name AS employee,
s.staff_name AS supervisor
FROM staff AS e
LEFT JOIN staff AS s
ON e.supervisor_id = s.staff_id
ORDER BY e.staff_id;
CROSS JOIN
A CROSS JOIN returns every possible combination of rows
from the two tables.
CREATE TABLE materials (
material TEXT
);
CREATE TABLE finishes (
finish TEXT
);
INSERT INTO materials VALUES
('Wood'),
('Metal'),
('Glass');
INSERT INTO finishes VALUES
('Matte'),
('Gloss');
SELECT
m.material,
f.finish
FROM materials AS m
CROSS JOIN finishes AS f
ORDER BY m.material, f.finish;
FULL OUTER JOIN Alternative in SQLite
For maximum compatibility with SQLite environments, a full outer join
can be simulated using two LEFT JOIN queries combined with
UNION.
CREATE TABLE left_items (
item_id INTEGER,
item_name TEXT
);
CREATE TABLE right_items (
item_id INTEGER,
item_name TEXT
);
INSERT INTO left_items VALUES
(1, 'Camera'),
(2, 'Tripod'),
(3, 'Microphone');
INSERT INTO right_items VALUES
(2, 'Tripod'),
(3, 'Microphone'),
(4, 'Headphones');
SELECT
l.item_id,
l.item_name AS left_item,
r.item_name AS right_item
FROM left_items AS l
LEFT JOIN right_items AS r
ON l.item_id = r.item_id
UNION
SELECT
r.item_id,
l.item_name AS left_item,
r.item_name AS right_item
FROM right_items AS r
LEFT JOIN left_items AS l
ON r.item_id = l.item_id
WHERE l.item_id IS NULL
ORDER BY item_id;
JOIN with DISTINCT
The DISTINCT keyword can remove duplicate values from
JOIN results.
CREATE TABLE musicians (
musician_id INTEGER,
musician_name TEXT
);
CREATE TABLE concerts (
concert_id INTEGER,
musician_id INTEGER,
venue TEXT
);
INSERT INTO musicians VALUES
(1, 'Lina'),
(2, 'Marco'),
(3, 'Sia');
INSERT INTO concerts VALUES
(101, 1, 'City Hall'),
(102, 1, 'City Hall'),
(103, 2, 'River Arena'),
(104, 3, 'City Hall');
SELECT DISTINCT
m.musician_name,
c.venue
FROM musicians AS m
INNER JOIN concerts AS c
ON m.musician_id = c.musician_id
ORDER BY m.musician_name;
Common SQL JOIN Types
| JOIN Type | Description | Result |
|---|---|---|
| INNER JOIN | Combines matching rows | Only matching records |
| LEFT JOIN | Keeps all rows from the left table | All left rows + matches |
| RIGHT JOIN | Keeps all rows from the right table | All right rows + matches |
| FULL OUTER JOIN | Keeps rows from both tables | Matches + unmatched rows |
| CROSS JOIN | Creates every combination | Cartesian product |
| SELF JOIN | Joins a table to itself | Related rows in one table |
Advantages of SQL JOINs
- Combines related information from different tables.
- Reduces the need to duplicate data across tables.
- Makes relational database queries more powerful.
- Works with filtering, sorting, grouping, and aggregation.
- Allows complex relationships between database tables to be queried.
Best Practices
- Always specify the relationship between tables using
ON. - Use table aliases to make long JOIN queries easier to read.
- Use INNER JOIN when only matching records are required.
- Use LEFT JOIN when all records from the primary table are needed.
- Select only the columns required for the result.
- Be careful with CROSS JOIN because it can produce many rows.
SQL JOINs allow you to connect related information stored in different tables. Understanding INNER JOIN, LEFT JOIN, CROSS JOIN, SELF JOIN, and other JOIN techniques is essential for working with relational databases.
🧪 Test Your SQL Code
Edit the SQL code on the left and click “Run Code” to see the result on the right.
Click “Run Code” to see the result here.