#06 MySQLデータベースを使ってみよう

テーブルを結合する — JOIN

なぜ JOIN が必要か

リレーショナルデータベースでは、データを正規化して複数のテーブルに分けて管理します。たとえば、注文情報にユーザーの名前をそのまま保存するのではなく、user_id だけを持ち、ユーザー情報は users テーブルから引いてくる構造にします。

これにより重複を排除し、データの一貫性を保てますが、取得時には複数テーブルを組み合わせる必要があります。これが JOIN? 複数のテーブルを結合して一つの結果セットにする操作。INNER JOIN(両方に存在する行のみ)・LEFT JOIN(左テーブルの全行 + 一致した右テーブル)などがある。 です。

JOIN の種類とベン図による比較 INNER JOIN users orders 一致 両テーブルで一致した行のみ ordersのないユーザーは含まれない user_idがNULLの注文も含まれない LEFT JOIN users 全行含む orders 左テーブル(users)の全行を返す 注文のないユーザーも含まれる 右テーブルが一致しない場合はNULL RIGHT JOIN users orders 全行含む 右テーブル(orders)の全行を返す user_idがNULLの注文も含まれる 左テーブルが一致しない場合はNULL SQL 構文の比較 INNER JOIN: SELECT u.name, o.product FROM users u INNER JOIN orders o ON o.user_id = u.id; LEFT JOIN: SELECT u.name, o.product FROM users u LEFT JOIN orders o ON o.user_id = u.id; RIGHT JOIN: SELECT u.name, o.product FROM users u RIGHT JOIN orders o ON o.user_id = u.id; RIGHT JOIN は LEFT JOIN で書き換えられるため、実務では LEFT JOIN が多く使われる
図1: INNER JOIN・LEFT JOIN・RIGHT JOINのベン図による比較

サンプルテーブルを用意する

CREATE TABLE users (
  id   INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  PRIMARY KEY (id)
);

CREATE TABLE orders (
  id         INT NOT NULL AUTO_INCREMENT,
  user_id    INT,
  product    VARCHAR(200) NOT NULL,
  total      DECIMAL(10, 2) NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  FOREIGN KEY (user_id) REFERENCES users(id)
);
INSERT INTO users (name) VALUES ('田中 太郎'), ('佐藤 花子'), ('鈴木 一郎'), ('高橋 美咲');

INSERT INTO orders (user_id, product, total) VALUES
  (1, 'ノートPC',    89800.00),
  (1, 'マウス',       3200.00),
  (2, 'キーボード',  12000.00),
  (3, 'モニター',    45000.00),
  (NULL, 'ケーブル',   800.00);  -- user_idがNULLの注文

INNER JOIN — 一致する行のみ取得

INNER JOIN? 結合条件が一致する行だけを返すJOIN。両テーブルに対応するデータが存在する場合のみ結果に含まれる。最もよく使われる結合方法。 は、両方のテーブルで結合条件が一致する行だけを返します。

SELECT
  users.name,
  orders.product,
  orders.total
FROM orders
INNER JOIN users ON orders.user_id = users.id;
+------------+--------------+----------+
| name       | product      | total    |
+------------+--------------+----------+
| 田中 太郎  | ノートPC     | 89800.00 |
| 田中 太郎  | マウス       |  3200.00 |
| 佐藤 花子  | キーボード   | 12000.00 |
| 鈴木 一郎  | モニター     | 45000.00 |
+------------+--------------+----------+

user_id = NULL の注文と、注文のない「高橋 美咲」は結果に含まれません。

users と orders の JOIN 例 usersテーブル id name 1 田中 太郎 2 佐藤 花子 3 鈴木 一郎 4 高橋 美咲 ← 注文なし 5 渡辺 健太 ordersテーブル id user_id product total 1 1 ノートPC 89800 2 1 マウス 3200 3 2 キーボード 12000 4 3 モニター 45000 5 5 ヘッドホン 22000 6 NULL ケーブル 800 ← user_idなし 7 5 マイク 18000 LEFT JOIN 結果(users LEFT JOIN orders ON orders.user_id = users.id) u.name o.product o.total 備考 田中 太郎 ノートPC 89800 田中 太郎 マウス 3200 佐藤 花子 キーボード 12000 鈴木 一郎 モニター 45000 高橋 美咲 NULL NULL ← 注文なし 渡辺 健太 ヘッドホン 22000 INNER JOIN の場合は「高橋 美咲」の行は結果に含まれない
図2: usersとordersをJOINした結果の例

LEFT JOIN — 左テーブルの全行を含む

LEFT JOIN? 左テーブルの全行を返し、右テーブルに一致するデータがない場合はNULLで補うJOIN。「注文がないユーザーも含めて一覧表示したい」といった用途に使う。 は、左側(FROM)のテーブルの全行を返します。右側に一致するデータがない場合は NULL で補完されます。

SELECT
  users.name,
  orders.product,
  orders.total
FROM users
LEFT JOIN orders ON orders.user_id = users.id;
+------------+--------------+----------+
| name       | product      | total    |
+------------+--------------+----------+
| 田中 太郎  | ノートPC     | 89800.00 |
| 田中 太郎  | マウス       |  3200.00 |
| 佐藤 花子  | キーボード   | 12000.00 |
| 鈴木 一郎  | モニター     | 45000.00 |
| 高橋 美咲  | NULL         |     NULL |
+------------+--------------+----------+

「注文をしたことがないユーザー」を探したいときは、LEFT JOINのNULLを活用します。

-- 一度も注文していないユーザーを抽出
SELECT users.name
FROM users
LEFT JOIN orders ON orders.user_id = users.id
WHERE orders.id IS NULL;

エイリアス(AS)の使い方

テーブル名やカラム名に別名をつけることで、クエリを簡潔に書けます。

-- テーブルにエイリアスをつける
SELECT
  u.name       AS ユーザー名,
  o.product    AS 商品名,
  o.total      AS 金額
FROM orders AS o
INNER JOIN users AS u ON o.user_id = u.id
ORDER BY o.total DESC;

AS は省略することもできます(orders o と書ける)が、明示した方が読みやすいです。


複数テーブルの JOIN

3つ以上のテーブルをJOINすることもできます。

-- categoriesテーブルも追加する例
CREATE TABLE categories (
  id   INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  PRIMARY KEY (id)
);

ALTER TABLE orders ADD COLUMN category_id INT;
ALTER TABLE orders ADD FOREIGN KEY (category_id) REFERENCES categories(id);
-- 3テーブルをJOIN
SELECT
  u.name       AS ユーザー名,
  o.product    AS 商品名,
  c.name       AS カテゴリ,
  o.total      AS 金額
FROM orders AS o
INNER JOIN users      AS u ON o.user_id      = u.id
INNER JOIN categories AS c ON o.category_id  = c.id
ORDER BY o.total DESC;

ON 句と WHERE 句の違い

JOINの条件は ON 句に、絞り込み条件は WHERE 句に書くのが基本ですが、INNER JOINでは結果が同じになる場合があります。LEFT JOINでは挙動が異なります。

-- ❌ LEFT JOINでWHEREに書くと、結果がINNER JOINと同じになる
SELECT u.name, o.product
FROM users AS u
LEFT JOIN orders AS o ON o.user_id = u.id
WHERE o.total > 10000;  -- NULLの行が除外されてしまう

-- ✅ LEFT JOINでの絞り込みはON句に書く
SELECT u.name, o.product
FROM users AS u
LEFT JOIN orders AS o ON o.user_id = u.id AND o.total > 10000;

まとめ

  • JOIN は複数テーブルのデータを結合して取得する仕組み
  • INNER JOIN: 両方のテーブルで一致する行のみ返す
  • LEFT JOIN: 左テーブルの全行を返し、右テーブルに一致がなければ NULL
  • エイリアス(AS)でクエリを短くできる
  • LEFT JOINの絞り込みは WHERE でなく ON 句に書く

次回はデータを集計する GROUP BY と集計関数を学びます。