【SQL入門の壁】JOINを完全攻略!INNER JOINとLEFT JOINの違いとは?
「社員名簿はあるけど、部署名は別のファイル…。社員名と部署名を一緒に一覧表示したい!」
「どの商品がどのカテゴリに属しているか、一目でわかるリストが欲しい!」
Webクリエイターとしてデータベースを扱う上で、必ずと言っていいほど直面するのが「複数のテーブルに散らばった情報を、一つにまとめて見たい」という課題です。顧客情報と購入履歴、商品マスタと在庫テーブルなど、正規化されたデータベースでは情報は目的ごとにテーブル分けされているのが普通です。
このバラバラの情報を、まるでパズルのピースをはめるように一つに繋ぎ合わせてくれるのが、SQLの強力な武器「JOIN(ジョイン)」です。JOINをマスターすれば、SQLで扱えるデータの幅が爆発的に広がり、より複雑で価値のある情報を引き出せるようになります。
この記事では、数あるJOINの中でも特に使用頻度の高いINNER JOINとLEFT JOINに焦点を当て、それぞれの役割と使い分けを、初心者の方でも絶対に理解できる図解とコピペOKなコードで徹底解説します!
準備:JOINを学ぶためのテーブル
JOINの動きを明確に理解するために、今回も「社員(employees)」テーブルと「部署(departments)」テーブルを使います。今回はJOINの違いを分かりやすくするため、特別に「まだ部署に配属されていない研修中の新人」と、「まだ社員が一人もいない新設の広報部」のデータを含めています。
以下のSQL文をコピーして、ご自身のデータベース環境で実行するか、後述の「SQL実行環境」で試してみてください。
-- テーブルが存在すれば削除
DROP TABLE IF EXISTS employees;
DROP TABLE IF EXISTS departments;
-- 部署テーブルの作成
CREATE TABLE departments (
id INT PRIMARY KEY,
department_name VARCHAR(50)
);
-- 部署データの挿入
INSERT INTO departments (id, department_name) VALUES
(1, '営業部'),
(2, '開発部'),
(3, '人事部'),
(4, '広報部'); -- 社員がまだいない部署
-- 社員テーブルの作成
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
department_id INT -- NULLを許可
);
-- 社員データの挿入
INSERT INTO employees (id, name, department_id) VALUES
(1, '山田 太郎', 1),
(2, '鈴木 花子', 2),
(3, '佐藤 次郎', 1),
(4, '高橋 三郎', 3),
(5, '田中 恵子', 2),
(6, '中村 さくら', NULL); -- まだ部署に配属されていない新人
準備はいいですか?2つのテーブルを繋ぎ合わせる旅に出ましょう!
1. INNER JOIN:2つのテーブルの共通部分だけを結合する
INNER JOINは、最も基本的で最もよく使われるJOINです。一言で言うと、「2つのテーブルの両方に存在する、関連するデータだけを抽出する」ための結合方法です。
ベン図で表すと、2つの円が重なる「共通部分」だけを取り出すイメージです。言い換えれば、「部署に所属している社員」の情報だけが欲しい、といったケースで使います。
基本的な使い方
FROM句で最初のテーブルを指定し、INNER JOINで2つ目のテーブルを指定します。そしてON句で、どの列をキー(目印)にしてテーブル同士を関連付けるかを定義します。
SELECT
e.name,
d.department_name
FROM
employees AS e
INNER JOIN
departments AS d ON e.department_id = d.id;
この結果を見てください。重要なポイントが2つあります。
- 部署に所属していない「中村 さくら」さんが結果に含まれていない。
- 社員が一人もいない「広報部」も結果に含まれていない。
これがINNER JOINの最大の特徴です。ON e.department_id = d.idという結合条件を満たす、つまり両方のテーブルに存在するデータだけが結果として返されます。
※ employees AS eのように、テーブル名の後にASをつけて別名(エイリアス)を定義すると、クエリを短く記述できて便利ですよ。
2. LEFT JOIN:片方のテーブルは全件表示する
LEFT JOINは、「左側のテーブル(FROM句で最初に指定したテーブル)のデータは全部表示し、それに関連する右側のテーブルのデータをくっつける」という結合方法です。LEFT OUTER JOINとも呼ばれますが、OUTERは省略可能です。
ベン図では、左側の円はすべて含み、右側の円は重なる部分だけ、というイメージです。もし右側のテーブルに対応するデータがなければ、その部分にはNULL(空っぽ)が入ります。
例1:「社員」を軸に全データを表示
「社員リストは全員分欲しい。もし部署に所属していれば、その部署名も知りたい」というケースを考えてみましょう。この場合、軸となる「社員(employees)」テーブルを左側に置きます。
SELECT
e.name,
d.department_name
FROM
employees AS e
LEFT JOIN
departments AS d ON e.department_id = d.id;
結果に注目してください!INNER JOINの時とは違い、部署が未定の「中村 さくら」さんが結果に含まれていますね。そして、彼女のdepartment_nameはNULLになっています。
これがLEFT JOINの力です。左側のemployeesテーブルのデータはすべて表示されるため、「まだ部署に紐付いていない社員を探す」といった用途に最適です。
例2:「部署」を軸に全データを表示
では、左側と右側のテーブルを入れ替えたらどうなるでしょうか?「全部署のリストが見たい。もし社員がいれば、その社員名も知りたい」というケースです。今度は「部署(departments)」テーブルを左側に置いてみましょう。
SELECT
d.department_name,
e.name
FROM
departments AS d
LEFT JOIN
employees AS e ON d.id = e.department_id;
今度の結果はまた違いますね!社員が一人もいない「広報部」がちゃんと表示され、対応する社員名(name)はNULLになっています。このように、LEFT JOINではどちらのテーブルを「左側」に置くかが非常に重要です。
INNER JOIN vs LEFT JOIN 使い分けのポイント
結局、どちらを使えばいいの?と迷ったら、自分が何を「主役」にしたいかを考えてみてください。
-
INNER JOIN: 「社員と部署、両方の情報が揃っているデータだけが見たい!」
→ 2つのテーブルの関連性を厳密に見たい場合に使う。 -
LEFT JOIN: 「社員は全員見たい。部署情報はあればでOK!」
→ 片方のテーブルを軸(マスター)として、すべてのレコードを表示させたい場合に使う。
この基準で考えれば、自然とどちらのJOINが適切か判断できるようになりますよ。
実践!ブラウザでJOINを試してみよう
お待たせしました!ここまで学んだINNER JOINとLEFT JOINを、実際に手を動かして試せる環境です。
下のコードをまるごとコピーして、join_practice.htmlのようなファイル名で保存し、ブラウザで開いてみてください。テーブルの左右を入れ替えたり、INNERとLEFTを切り替えたりして、結果がどう変わるか体感してみてください!
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>SQL JOIN練習場</title>
<script src="https://cdn.jsdelivr.net/npm/alasql@4"></script>
<style>
body { font-family: sans-serif; padding: 2rem; background-color: #f9f9f9; }
.container { max-width: 800px; margin: auto; background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
h1 { color: #333; }
textarea { width: 100%; height: 180px; font-family: monospace; font-size: 16px; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; margin-bottom: 1rem; }
button { background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
button:hover { background-color: #0056b3; }
#result-area { margin-top: 2rem; }
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.error { color: red; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h1>JOINを試してみよう!</h1>
<p>下のテキストエリアにSQL文を入力して「実行」ボタンを押してください。</p>
<textarea id="sql-input">-- この中にSQLを書いてください
SELECT
e.name,
d.department_name
FROM
employees AS e
LEFT JOIN
departments AS d ON e.department_id = d.id;</textarea>
<button onclick="executeSQL()">SQLを実行</button>
<div id="result-area"></div>
</div>
<script>
// データベースの初期化とデータ準備
const db = new alasql.Database();
db.exec(`
CREATE TABLE departments (id INT, department_name STRING);
INSERT INTO departments VALUES (1, '営業部'), (2, '開発部'), (3, '人事部'), (4, '広報部');
CREATE TABLE employees (id INT, name STRING, department_id INT);
INSERT INTO employees VALUES (1, '山田 太郎', 1), (2, '鈴木 花子', 2), (3, '佐藤 次郎', 1), (4, '高橋 三郎', 3), (5, '田中 恵子', 2), (6, '中村 さくら', NULL);
`);
function executeSQL() {
const sql = document.getElementById('sql-input').value;
const resultArea = document.getElementById('result-area');
resultArea.innerHTML = '';
try {
const result = db.exec(sql);
if (result.length > 0) {
resultArea.appendChild(createTable(result));
} else {
resultArea.innerHTML = '<p>結果は0件でした。</p>';
}
} catch (e) {
resultArea.innerHTML = `<p class="error">エラー: ${e.message}</p>`;
}
}
function createTable(data) {
const table = document.createElement('table');
const thead = table.createTHead();
const tbody = table.createTBody();
const headerRow = thead.insertRow();
for (const key in data[0]) {
const th = document.createElement('th');
th.textContent = key;
headerRow.appendChild(th);
}
data.forEach(rowData => {
const row = tbody.insertRow();
for (const key in rowData) {
const cell = row.insertCell();
cell.textContent = (rowData[key] === null) ? 'NULL' : rowData[key];
}
});
return table;
}
// 初期表示
executeSQL();
</script>
</body>
</html>
気をつけるべき点と関連テクニック
ONとWHEREの違い
初心者が混乱しがちなのがONとWHEREの使い分けです。シンプルに覚えましょう。
- ON: テーブル同士を「どのキーで繋ぐか」という結合のルールを定義する。
- WHERE: 結合して出来上がった一つの大きなテーブルから、「どの行を絞り込むか」というフィルタリングのルールを定義する。
まずはONでテーブルを正しく繋ぎ、その後にWHEREで必要なデータだけを絞り込む、という流れを意識してください。
RIGHT JOINとFULL OUTER JOIN
JOINには他にも仲間がいます。
- RIGHT JOIN:
LEFT JOINの逆バージョンです。右側のテーブルを軸に全件表示します。 - FULL OUTER JOIN: 左右両方のテーブルのデータをすべて表示し、対応するデータがない場合は
NULLを入れます。
実務ではLEFT JOINを圧倒的によく使いますが、こんな仲間もいるんだな、と頭の片隅に置いておくと良いでしょう。
まとめ
お疲れ様でした!テーブル結合の基本であるINNER JOINとLEFT JOINについて、その違いと使い分けを解説しました。
- INNER JOIN: 2つのテーブルの共通部分だけが欲しいときに使う。関連のないデータは結果から除外される。
- LEFT JOIN: 片方のテーブルを主役に、全データを表示させたいときに使う。関連データがない場合は
NULLで補完される。 - JOINの使い分けは、「何を主役にしてデータを見たいか」で決める!
JOINは、リレーショナルデータベースの真価を発揮させるための核となる機能です。最初は少し難しく感じるかもしれませんが、使えば使うほどその便利さが分かり、データを取り出すのがどんどん楽しくなります。ぜひ、色々なパターンを試して、JOINを自分の武器にしてください!