前回までに CRUD(SELECT / INSERT / UPDATE / DELETE)の基本を学びました。今回は、複数のテーブルをつなげてデータを取得する JOIN を扱います。
引き続き DB Browser for SQLite(practice.db)を使います。
なぜテーブルを分けるのか
これまでの employees テーブルでは、部署名を文字列でそのまま入れていました。
| id | name | department | age |
|---|---|---|---|
| 1 | 田中太郎 | 開発部 | 28 |
| 2 | 鈴木花子 | 営業部 | 32 |
| 3 | 佐藤次郎 | 開発部 | 25 |
この方法には問題があります。
- 「開発部」の名前を「開発1部」に変えたい場合、全行を UPDATE しなければならない
- 入力ミスで「開発部」と「開発 部」(スペース入り)が混ざると、別の部署として扱われる
解決策は、部署の情報を別のテーブルに分けることです。employees テーブルには部署の ID だけを持たせ、部署名は departments テーブルで管理します。
2つのテーブルの関係を図にします。
erDiagram
departments {
INTEGER id PK
TEXT name
}
employees {
INTEGER id PK
TEXT name
INTEGER department_id FK
INTEGER age
}
departments ||--o{ employees : "1つの部署に複数の社員"
2つのテーブルを準備する
まず既存の employees テーブルを削除して、新しい構造で作り直します。
DROP TABLE IF EXISTS employees;
部署テーブルを作ります。
CREATE TABLE departments (
id INTEGER PRIMARY KEY,
name TEXT
);
INSERT INTO departments (id, name) VALUES (1, '開発部');
INSERT INTO departments (id, name) VALUES (2, '営業部');
INSERT INTO departments (id, name) VALUES (3, '総務部');
社員テーブルを作ります。department 列のかわりに department_id 列を持たせます。
CREATE TABLE employees (
id INTEGER PRIMARY KEY,
name TEXT,
department_id INTEGER,
age INTEGER
);
INSERT INTO employees (id, name, department_id, age) VALUES (1, '田中太郎', 1, 28);
INSERT INTO employees (id, name, department_id, age) VALUES (2, '鈴木花子', 2, 32);
INSERT INTO employees (id, name, department_id, age) VALUES (3, '佐藤次郎', 1, 25);
INSERT INTO employees (id, name, department_id, age) VALUES (4, '山田美咲', 3, 29);
INSERT INTO employees (id, name, department_id, age) VALUES (5, '高橋健一', 2, 35);
department_id の 1 は departments テーブルの id = 1(開発部)に対応します。このように、別テーブルの行を指し示す列を外部キーと呼びます。
INNER JOIN でテーブルをつなぐ
employees テーブルだけを SELECT すると、department_id は数字のままです。
SELECT * FROM employees;
| id | name | department_id | age |
|---|---|---|---|
| 1 | 田中太郎 | 1 | 28 |
| 2 | 鈴木花子 | 2 | 32 |
部署名を表示したいときは、departments テーブルと結合します。
SELECT employees.name, departments.name, employees.age
FROM employees
INNER JOIN departments ON employees.department_id = departments.id;
| employees.name | departments.name | employees.age |
|---|---|---|
| 田中太郎 | 開発部 | 28 |
| 鈴木花子 | 営業部 | 32 |
| 佐藤次郎 | 開発部 | 25 |
| 山田美咲 | 総務部 | 29 |
| 高橋健一 | 営業部 | 35 |
SQL の構造を分解します。
FROM employees— ベースのテーブルを指定INNER JOIN departments— 結合するテーブルを指定ON employees.department_id = departments.id— 結合条件(「employees の department_id と departments の id が一致する行をつなげる」)
テーブル名.列名 の形で、どちらのテーブルの列かを明示します。
Java で同じことをすると
Java で2つの ArrayList を結合するイメージです。
for (Employee e : employees) {
for (Department d : departments) {
if (e.departmentId == d.id) {
System.out.println(e.name + " " + d.name);
}
}
}
二重ループと条件一致で探す処理を、SQL では JOIN と ON の1文で書けます。
別名をつけて短く書く
テーブル名が長いと SQL も長くなります。AS で別名をつけると短く書けます。
SELECT e.name, d.name, e.age
FROM employees AS e
INNER JOIN departments AS d ON e.department_id = d.id;
e が employees、d が departments の略です。結果は同じですが、読みやすくなります。
WHERE と組み合わせる
JOIN した結果をさらに絞り込むこともできます。
SELECT e.name, d.name, e.age
FROM employees AS e
INNER JOIN departments AS d ON e.department_id = d.id
WHERE d.name = '開発部';
| e.name | d.name | e.age |
|---|---|---|
| 田中太郎 | 開発部 | 28 |
| 佐藤次郎 | 開発部 | 25 |
JOIN でテーブルを結合 → WHERE で絞り込み、という順番です。
LEFT JOIN — 一致しない行も表示する
もう1人、部署が決まっていない社員を追加します。
INSERT INTO employees (id, name, department_id, age) VALUES (6, '中村亮', NULL, 27);
department_id が NULL(値なし)です。INNER JOIN では NULL は一致条件を満たさないため、この社員は結果に含まれません。
SELECT e.name, d.name
FROM employees AS e
INNER JOIN departments AS d ON e.department_id = d.id;
中村亮は表示されません。
LEFT JOIN を使うと、左側のテーブル(employees)の全行が表示されます。
SELECT e.name, d.name
FROM employees AS e
LEFT JOIN departments AS d ON e.department_id = d.id;
| e.name | d.name |
|---|---|
| 田中太郎 | 開発部 |
| 鈴木花子 | 営業部 |
| 佐藤次郎 | 開発部 |
| 山田美咲 | 総務部 |
| 高橋健一 | 営業部 |
| 中村亮 | (NULL) |
中村亮の部署名は NULL になりますが、行自体は表示されます。「一致しないものも含めて全件見たい」ときに LEFT JOIN を使います。
ここまでのまとめ
| JOIN の種類 | 動作 |
|---|---|
| INNER JOIN | 両テーブルで条件が一致する行だけ返す |
| LEFT JOIN | 左テーブルの全行を返す(右が NULL でも含む) |
| 概念 | 意味 |
|---|---|
| 外部キー | 別テーブルの行を指す列(department_id → departments.id) |
| ON 条件 | 結合するための一致条件 |
| AS | テーブルや列に別名をつける |
DB コースのまとめと次へ
ここまでで学んだ SQL の範囲をまとめます。
| 内容 | 扱った SQL |
|---|---|
| DB の基本概念と環境導入 | — |
| データの取得・絞り込み・並び替え | SELECT / WHERE / ORDER BY |
| データの追加・更新・削除 | INSERT / UPDATE / DELETE |
| テーブルの結合と外部キー | JOIN / ON |
次の回へ
INNER JOIN と LEFT JOIN の結果の違いを DB Browser で確認できたら、このコースは完了です。次は HTTP / REST / JSON の最小マップ で、ブラウザとサーバーのやり取りを短く整理します。そのあと Spring Boot を使い、ここまで学んだ SQL が Spring Data JPA の CRUD 操作とどう対応しているかを見ていきます。