SQL / LESSON 09 / SQL09

SQL:テーブルの結合(INNER JOIN)

1学習の目的

2基礎解説

実務のデータベースは複数のテーブルに分かれています。注文テーブルには商品名も顧客名も入っておらず、IDだけが記録されています。それをつなぐのが JOIN です。

⚠ この章から2つのテーブルを追加します

下のSQLを実行環境で流してから始めてください。

CREATE TABLE customers (
  id   INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  area TEXT
);

INSERT INTO customers VALUES
 (1, '田中商事', '東京'),
 (2, '鈴木工業', '大阪'),
 (3, '佐藤物産', '東京'),
 (4, '高橋建設', '福岡');

CREATE TABLE orders (
  id          INTEGER PRIMARY KEY,
  customer_id INTEGER,
  product_id  INTEGER,
  qty         INTEGER,
  order_date  TEXT
);

INSERT INTO orders VALUES
 (1, 1, 1,  2, '2026-01-05'),
 (2, 1, 2,  5, '2026-01-08'),
 (3, 2, 4,  1, '2026-01-12'),
 (4, 3, 2, 10, '2026-01-15'),
 (5, 1, 3,  3, '2026-02-03'),
 (6, 2, 5, 20, '2026-02-10'),
 (7, 3, 1,  1, '2026-02-14');
テーブル内容他とのつながり
products商品マスタ(7件)id
customers顧客マスタ(4件)id
orders注文履歴(7件)customer_id / product_id
✅ 覚えるべき重要ポイント
  1. JOIN は「つなぐ条件」を ON で指定するON orders.product_id = products.id のように、どの列とどの列が対応するかを書く。
  2. テーブルに別名を付けると短く書ける。FROM orders o とすれば、以降 o.qty と書ける。
  3. 同じ列名が複数テーブルにあるときは必ず「テーブル名.列名」で指定するname だけでは商品名か顧客名か分からずエラーになる。
  4. INNER JOIN は「両方に存在する行」だけを返す。注文が1件もない顧客は結果から消える。
  5. JOIN はいくつでもつなげられる。注文 → 顧客 → 商品と3つ結合すれば、「誰が何を買ったか」が1行になる。

現場使用例:注文明細に商品名を表示、会員情報と購入履歴の紐付け、部署名つきの社員一覧。実務のSQLでJOINが出てこないことはまずない

-- 注文に商品名を付ける
SELECT o.id, p.name, o.qty
FROM orders o
INNER JOIN products p ON o.product_id = p.id;

-- 金額まで計算する
SELECT o.id, p.name, o.qty, p.price * o.qty AS 金額
FROM orders o
INNER JOIN products p ON o.product_id = p.id;

-- 3つのテーブルをつなぐ
SELECT c.name AS 顧客, p.name AS 商品, o.qty
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products  p ON o.product_id  = p.id;

-- 結合してから集計する
SELECT c.name AS 顧客, SUM(p.price * o.qty) AS 売上
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products  p ON o.product_id  = p.id
GROUP BY c.name
ORDER BY 売上 DESC;

3基本ドリル(10問)

SQL09-D01 orders と products を結合し、注文ID(o.id)と商品名(p.name)と数量(o.qty)を取り出すSQLを書け。 コーディング★☆☆無料
期待される結果

3列7行。1 ノートPC 2 が先頭

ヒント

FROM orders o INNER JOIN products p ON o.product_id = p.id と書く。

模範解答
SELECT o.id, p.name, o.qty
FROM orders o
INNER JOIN products p ON o.product_id = p.id;
解説

注文テーブルには商品名が入っていない。product_id だけが記録されており、JOIN で商品マスタとつないで初めて名前が分かる。

SQL09-D02 JOIN で「つなぐ条件」を指定するキーワードはどれか。
A. WITH B. ON C. AS D. BY
選択★☆☆無料
期待される結果

解答 == B

ヒント

「〜において」を意味する2文字。

模範解答
B
解説

ON で「どの列とどの列が対応するか」を指定する。これを書き忘れると、全行と全行が掛け合わされた巨大な結果になる。

SQL09-D03 orders と customers を結合し、注文ID(o.id)と顧客名(c.name)を取り出すSQLを書け。 コーディング★☆☆無料
期待される結果

2列7行。1 田中商事 が先頭

ヒント

ON o.customer_id = c.id でつなぐ。

模範解答
SELECT o.id, c.name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id;
解説

つなぐ列の名前は違ってよい。orders 側は customer_id、customers 側は id——名前が違っても、対応関係さえ正しければつながる。

SQL09-D04 注文に商品名を付けるSQLを完成させよ。 穴埋め★☆☆無料
コード
SELECT o.id, p.name
FROM orders o
INNER JOIN products p ____ o.product_id = p.id;
期待される結果

2列7行

ヒント

結合条件を導く2文字のキーワード。

模範解答
ON
解説

ON の後ろに書くのが結合条件。WHERE の条件とは役割が違い、「行を絞る」のではなく「行をつなぐ」ためのもの。

SQL09-D05 INNER JOIN で、注文が1件もない顧客はどうなるか。
A. 結果に含まれる B. 結果から消える C. NULLとして表示される D. エラーになる
選択★☆☆無料
期待される結果

解答 == B

ヒント

INNER は「内側」。両方にあるものだけ。

模範解答
B
解説

両方のテーブルに対応がある行だけが残る。高橋建設は注文が0件なので、結果に現れない。次章の LEFT JOIN を使うと残せる

SQL09-D06 注文ごとの金額(単価 × 数量)を計算し、注文ID・商品名・数量・金額(別名「金額」)を取り出すSQLを書け。 コーディング★★☆無料
期待される結果

4列7行。1 ノートPC 2 256000

ヒント

p.price * o.qty で計算する。テーブル別名を付けると読みやすい。

模範解答
SELECT o.id, p.name, o.qty, p.price * o.qty AS 金額
FROM orders o
INNER JOIN products p ON o.product_id = p.id;
解説

金額はどこにも保存されていない。単価は商品テーブル、数量は注文テーブルにあり、結合して初めて計算できる。

SQL09-D07 テーブルに別名を付けて短く書くSQLを完成させよ。 穴埋め★★☆無料
コード
SELECT o.id, p.name
FROM orders ____
INNER JOIN products p ON o.product_id = p.id;
期待される結果

2列7行

ヒント

orders の別名。SELECT の中で使っている1文字。

模範解答
o
解説

テーブル名の直後に書くだけで別名になる(AS は省略可)。長いテーブル名を何度も書かずに済み、SQLが読みやすくなる。

SQL09-D08 products と customers の両方に name 列がある。結合したSQLで SELECT name と書くとどうなるか。
A. 商品名が返る B. 顧客名が返る C. どちらか判別できずエラー D. 両方返る
選択★★☆無料
期待される結果

解答 == C

ヒント

同じ名前の列が2つあるとき、SQLはどちらを選べるか。

模範解答
C
解説

「ambiguous column name」というエラーになる。p.name のようにテーブルを明示すれば解決する。

SQL09-D09 3つのテーブルを結合し、顧客名(別名「顧客」)・商品名(別名「商品」)・数量(o.qty)を取り出すSQLを書け。 コーディング★★☆無料
期待される結果

3列7行。田中商事 ノートPC 2 が先頭

ヒント

INNER JOIN を2回書く。customers と products の両方につなぐ。

模範解答
SELECT c.name AS 顧客, p.name AS 商品, o.qty
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products  p ON o.product_id  = p.id;
解説

JOIN はいくつでもつなげられる。IDしか持たない注文テーブルが、人間に読める形に変わった。

SQL09-D10 商品ごとの注文数量の合計を、商品名(別名「商品」)と合計数量(別名「数量」)で取り出し、数量の多い順に並べるSQLを書け。 コーディング★★☆無料
期待される結果

2列5行。USBメモリ 20 が先頭

ヒント

結合してから GROUP BY p.name で集計する。

模範解答
SELECT p.name AS 商品, SUM(o.qty) AS 数量
FROM orders o
INNER JOIN products p ON o.product_id = p.id
GROUP BY p.name
ORDER BY 数量 DESC;
解説

結合してから集計できる。JOIN で1つの表にまとめてしまえば、これまで学んだ GROUP BY がそのまま使える。

4実践シナリオ(5問)

SQL09-S01 【注文明細一覧】注文日(別名「日付」)・顧客名(別名「顧客」)・商品名(別名「商品」)・数量(別名「数量」)・金額(単価×数量、別名「金額」)を、注文ID順に取り出すSQLを書け。 コーディング★★☆無料
期待される結果

5列7行。2026-01-05 田中商事 ノートPC 2 256000 が先頭

ヒント

3テーブルを結合し、ORDER BY o.id で並べる。

模範解答
SELECT
  o.order_date AS 日付,
  c.name AS 顧客,
  p.name AS 商品,
  o.qty AS 数量,
  p.price * o.qty AS 金額
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products  p ON o.product_id  = p.id
ORDER BY o.id;
解説

これが「注文明細」そのもの。3つのテーブルに分かれて保存されたデータが、1枚の伝票として組み上がった。

SQL09-S02 【顧客別の売上】顧客名(別名「顧客」)ごとに、注文件数(別名「件数」)と売上合計(別名「売上」)を取り出し、売上の多い順に並べるSQLを書け。 コーディング★★☆無料
期待される結果

3列3行。田中商事 3 297500 が先頭

ヒント

3テーブルを結合してから GROUP BY c.name。売上は SUM(p.price * o.qty)。

模範解答
SELECT
  c.name AS 顧客,
  COUNT(*) AS 件数,
  SUM(p.price * o.qty) AS 売上
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products  p ON o.product_id  = p.id
GROUP BY c.name
ORDER BY 売上 DESC;
解説

顧客は4社あるのに3行しか出ない。高橋建設は注文が0件なので、INNER JOIN では消えてしまう。「売上0円の顧客」を出したいなら次章のLEFT JOINが要る

SQL09-S03 【エリア別の売上】顧客のエリア(別名「エリア」)ごとに、売上合計(別名「売上」)を取り出し、売上の多い順に並べるSQLを書け。 コーディング★★☆無料
期待される結果

2列2行。東京 457500 / 大阪 75000

ヒント

GROUP BY c.area で集計する。福岡の高橋建設は注文がないため現れない。

模範解答
SELECT
  c.area AS エリア,
  SUM(p.price * o.qty) AS 売上
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products  p ON o.product_id  = p.id
GROUP BY c.area
ORDER BY 売上 DESC;
解説

顧客マスタの属性で集計できるのがJOINの威力。注文テーブルにはエリア情報が一切ないのに、エリア別の売上が出せている。

SQL09-S04 【高額注文の抽出】金額(単価×数量)が 5万円以上の注文について、顧客名(別名「顧客」)・商品名(別名「商品」)・金額(別名「金額」)を、金額の大きい順に取り出すSQLを書け。 コーディング★★★無料
期待される結果

3列2行。田中商事 ノートPC 256000 / 佐藤物産 ノートPC 128000

ヒント

WHERE p.price * o.qty >= 50000 で絞る。WHERE は結合の後に効く。

模範解答
SELECT
  c.name AS 顧客,
  p.name AS 商品,
  p.price * o.qty AS 金額
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products  p ON o.product_id  = p.id
WHERE p.price * o.qty >= 50000
ORDER BY 金額 DESC;
解説

結合した結果に対して WHERE が効く。単価も数量も別テーブルにあるが、つないだ後なら1つの表として条件を書ける。

SQL09-S05 【商品別の売上ランキング】商品名(別名「商品」)ごとに、注文件数(別名「注文数」)・合計数量(別名「数量」)・売上(別名「売上」)を取り出し、売上の多い順の上位3件を出すSQLを書け。 コーディング★★★無料
期待される結果

4列3行。ノートPC 2 3 384000 が先頭

ヒント

結合 → GROUP BY p.name → ORDER BY 売上 DESC → LIMIT 3。

模範解答
SELECT
  p.name AS 商品,
  COUNT(*) AS 注文数,
  SUM(o.qty) AS 数量,
  SUM(p.price * o.qty) AS 売上
FROM orders o
INNER JOIN products p ON o.product_id = p.id
GROUP BY p.name
ORDER BY 売上 DESC
LIMIT 3;
解説

数量とお金は別の順位になる。数量ではUSBメモリが20個で最多だが、売上では3万円で下位。何を「売れている」と呼ぶかで、答えが変わる

5仕上げ課題

SQL09-FINAL 【顧客別 売上レポート】営業部から「顧客ごとの取引状況をまとめてほしい」と依頼された。

表示する列
① 顧客名 → 別名「顧客」
② エリア → 別名「エリア」
③ 注文件数 → 別名「注文件数」
④ 購入した商品の種類数(重複を除く)→ 別名「商品種類」
⑤ 売上合計(単価 × 数量)→ 別名「売上」
⑥ 平均注文額(売上 ÷ 注文件数、小数第1位まで)→ 別名「平均注文額」

条件:売上が 10万円 以上の顧客のみ
並び順:売上の大きい順

期待される結果(6列 × 2行)
顧客 | エリア | 注文件数 | 商品種類 | 売上 | 平均注文額
田中商事 | 東京 | 3 | 3 | 297500 | 99166.7
佐藤物産 | 東京 | 2 | 2 | 160000 | 80000.0
期待される結果

6列 × 2行が完全一致

ヒント

3テーブルを結合し、GROUP BY で顧客ごとにまとめる。商品種類は COUNT(DISTINCT p.id)、平均注文額は ROUND(SUM(p.price*o.qty) * 1.0 / COUNT(*), 1)。売上10万円以上は集計後の条件なので HAVING。

模範解答
SELECT
  c.name AS 顧客,
  c.area AS エリア,
  COUNT(*) AS 注文件数,
  COUNT(DISTINCT p.id) AS 商品種類,
  SUM(p.price * o.qty) AS 売上,
  ROUND(SUM(p.price * o.qty) * 1.0 / COUNT(*), 1) AS 平均注文額
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products  p ON o.product_id  = p.id
GROUP BY c.name, c.area
HAVING SUM(p.price * o.qty) >= 100000
ORDER BY 売上 DESC;
解説

3つのテーブル、8つの句が1本のSQLに集約された。JOIN・GROUP BY・HAVING・ORDER BY・集計関数・DISTINCT——ここまで学んだすべてが1つのクエリに入っている。これが実務のレポートSQLの標準的な姿だ。

* 1.0 という書き方に注目してほしい。SQLiteでは整数どうしの割り算が整数になってしまうため、小数で計算させるために 1.0 を掛けている。297500 ÷ 3 は本来 99166.67 だが、これを書かないと 99166 になる。数字がわずかに違うだけのバグは、気づきにくく影響が大きい

そして最も重要なのは、この結果に高橋建設が現れていないことだ。売上10万円未満だからではない。注文が1件もないため、INNER JOIN の段階で消えている。もし「取引のない顧客も含めた全社リスト」を求められていたら、このSQLは誤った報告になる。

次章では LEFT JOIN を学ぶ。「対応がない行も残す」結合で、高橋建設を売上0円として表示できるようになる。INNER と LEFT のどちらを選ぶかは、業務の要求次第——その判断ができて初めて、JOINを理解したと言える。