🇯🇵 日本語 | 🇺🇸 English | 🇪🇸 Español | 🇵🇹 Português | 🇹🇭 ไทย | 🇨🇳 中文

【SQL入門の壁】JOINを完全攻略!INNER JOINとLEFT JOINの違いとは?

「社員名簿はあるけど、部署名は別のファイル…。社員名と部署名を一緒に一覧表示したい!」
「どの商品がどのカテゴリに属しているか、一目でわかるリストが欲しい!」

Webクリエイターとしてデータベースを扱う上で、必ずと言っていいほど直面するのが「複数のテーブルに散らばった情報を、一つにまとめて見たい」という課題です。顧客情報と購入履歴、商品マスタと在庫テーブルなど、正規化されたデータベースでは情報は目的ごとにテーブル分けされているのが普通です。

このバラバラの情報を、まるでパズルのピースをはめるように一つに繋ぎ合わせてくれるのが、SQLの強力な武器「JOIN(ジョイン)」です。JOINをマスターすれば、SQLで扱えるデータの幅が爆発的に広がり、より複雑で価値のある情報を引き出せるようになります。

この記事では、数あるJOINの中でも特に使用頻度の高いINNER JOINLEFT 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_nameNULLになっています。

これが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 使い分けのポイント

結局、どちらを使えばいいの?と迷ったら、自分が何を「主役」にしたいかを考えてみてください。

この基準で考えれば、自然とどちらのJOINが適切か判断できるようになりますよ。


実践!ブラウザでJOINを試してみよう

お待たせしました!ここまで学んだINNER JOINLEFT JOINを、実際に手を動かして試せる環境です。

下のコードをまるごとコピーして、join_practice.htmlのようなファイル名で保存し、ブラウザで開いてみてください。テーブルの左右を入れ替えたり、INNERLEFTを切り替えたりして、結果がどう変わるか体感してみてください!


<!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の違い

初心者が混乱しがちなのがONWHEREの使い分けです。シンプルに覚えましょう。

まずはONでテーブルを正しく繋ぎ、その後にWHEREで必要なデータだけを絞り込む、という流れを意識してください。


RIGHT JOINとFULL OUTER JOIN

JOINには他にも仲間がいます。

実務ではLEFT JOINを圧倒的によく使いますが、こんな仲間もいるんだな、と頭の片隅に置いておくと良いでしょう。


まとめ

お疲れ様でした!テーブル結合の基本であるINNER JOINLEFT JOINについて、その違いと使い分けを解説しました。

JOINは、リレーショナルデータベースの真価を発揮させるための核となる機能です。最初は少し難しく感じるかもしれませんが、使えば使うほどその便利さが分かり、データを取り出すのがどんどん楽しくなります。ぜひ、色々なパターンを試して、JOINを自分の武器にしてください!