📋 このステップで学ぶこと
第3正規形(3NF)の定義と概念
推移的関数従属性とは何か
第2正規形から第3正規形への変換方法
3NFのメリットとデメリット
実践的な正規化の例題
学習時間の目安: 2.5時間 | 前提知識: STEP 9修了
🎯 1. 第3正規形(3NF)とは?
STEP 9からの続き:正規化の第三歩
STEP 8で第1正規形(繰り返し項目の排除)、STEP 9で第2正規形(部分関数従属性の排除)を学びました。このSTEP 10では、正規化の最終段階である第3正規形 を学び、推移的関数従属性 を排除していきます。
第3正規形の定義
📝 第3正規形とは
第3正規形(Third Normal Form, 3NF) =第2正規形を満たし、かつ推移的関数従属性が存在しない 状態
💡 簡単に言うと
第3正規形とは、「主キーに直接関係ないデータを別テーブルに分離する」 ということです。
つまり、「〇〇を知るために△△を経由する」という間接的な関係をなくす のが第3正規形です。
復習:これまでの正規化
第1正規形(1NF)
繰り返し項目を排除し、各セルに1つの値だけ を持たせる
第2正規形(2NF)
部分関数従属性を排除 し、すべての非キー属性が主キー全体に依存するようにする
第3正規形(3NF)
推移的関数従属性を排除 し、主キー以外への依存をなくす
🔄 2. 推移的関数従属性とは?
推移的関数従属性の定義
📝 推移的関数従属性とは
推移的関数従属性(Transitive Functional Dependency) =「AがBを決定し、BがCを決定する」という間接的な依存関係
推移的関数従属性の例
📊 従業員テーブル(第2正規形までは満たしている)
employees(第2正規形)
——————————————————-
従業員ID | 従業員名 | 部署コード | 部署名
1 | 山田太郎 | D001 | 営業部
2 | 佐藤花子 | D002 | 開発部
3 | 鈴木一郎 | D001 | 営業部
依存関係の分析
従業員ID → 部署コード (従業員IDが決まれば部署コードが決まる)
部署コード → 部署名 (部署コードが決まれば部署名が決まる)
つまり 従業員ID → 部署コード → 部署名 という推移的な依存 がある
⚠️ 推移的関数従属性の問題点
更新異常 :「営業部」の名前を「販売部」に変更する場合、D001を持つ全従業員のレコードを更新する必要がある
挿入異常 :新しい部署「D003:人事部」を登録したいが、従業員がいないと登録できない
削除異常 :従業員を削除すると、部署情報も失われる可能性がある
データの冗長性 :「D001:営業部」という情報が複数行に重複している
推移的関数従属性を図で理解する
❌ 推移的関数従属性がある状態(第2正規形)
従業員ID ──→ 部署コード ──→ 部署名
└─────────────────────────┘
間接的な依存(推移的)
✅ 推移的関数従属性を排除した状態(第3正規形)
従業員ID ──→ 部署コード
部署コード ──→ 部署名
それぞれ独立したテーブルで管理
🔧 3. 第2正規形から第3正規形への変換
推移的関数従属性を排除するには、間接的に依存しているデータを別テーブルに分離 します。
変換手順
📋 第3正規形への変換ステップ
推移的関数従属性を見つける (A → B → C の関係を探す)
間接的に依存しているカラムを別テーブルに分離 する
元のテーブルには参照用の外部キーだけを残す
実例1:従業員テーブルの正規化
❌ 第2正規形(推移的関数従属性あり)
employees
——————————————————-
従業員ID | 従業員名 | 部署コード | 部署名 | 部署電話
1 | 山田太郎 | D001 | 営業部 | 03-1111-2222
2 | 佐藤花子 | D002 | 開発部 | 03-3333-4444
3 | 鈴木一郎 | D001 | 営業部 | 03-1111-2222
【問題点】
従業員ID → 部署コード → 部署名、部署電話(推移的関数従属性)
部署情報が重複している
✅ 第3正規形(推移的関数従属性を排除)
【employees(従業員テーブル)】
—————————————
従業員ID | 従業員名 | 部署コード
1 | 山田太郎 | D001
2 | 佐藤花子 | D002
3 | 鈴木一郎 | D001
【departments(部署テーブル)】
—————————————
部署コード | 部署名 | 部署電話
D001 | 営業部 | 03-1111-2222
D002 | 開発部 | 03-3333-4444
✅ 改善点
部署情報は1回だけ記録される
部署名変更はdepartmentsテーブルだけ更新すればOK
従業員がいなくても部署情報を登録できる
データの整合性が保証される
実例2:商品テーブルの正規化
❌ 第2正規形(推移的関数従属性あり)
products
———————————————————————–
商品ID | 商品名 | カテゴリコード | カテゴリ名 | 税率
101 | ノートPC | C001 | 電子機器 | 0.10
102 | マウス | C001 | 電子機器 | 0.10
103 | ボールペン | C002 | 文房具 | 0.08
【問題点】
商品ID → カテゴリコード → カテゴリ名、税率(推移的関数従属性)
✅ 第3正規形(推移的関数従属性を排除)
【products(商品テーブル)】
—————————————
商品ID | 商品名 | カテゴリコード
101 | ノートPC | C001
102 | マウス | C001
103 | ボールペン | C002
【categories(カテゴリテーブル)】
—————————————
カテゴリコード | カテゴリ名 | 税率
C001 | 電子機器 | 0.10
C002 | 文房具 | 0.08
実例3:書籍管理テーブルの正規化
❌ 第2正規形(推移的関数従属性あり)
books
————————————————————————-
書籍ID | 書籍名 | 著者ID | 著者名 | 著者国籍
1 | データベース入門 | A001 | 山田太郎 | 日本
2 | SQL実践 | A001 | 山田太郎 | 日本
3 | Python入門 | A002 | 佐藤花子 | 日本
【問題点】
書籍ID → 著者ID → 著者名、著者国籍(推移的関数従属性)
✅ 第3正規形(推移的関数従属性を排除)
【books(書籍テーブル)】
—————————————
書籍ID | 書籍名 | 著者ID
1 | データベース入門 | A001
2 | SQL実践 | A001
3 | Python入門 | A002
【authors(著者テーブル)】
—————————————
著者ID | 著者名 | 著者国籍
A001 | 山田太郎 | 日本
A002 | 佐藤花子 | 日本
💡 4. 第3正規形のメリットとデメリット
メリット
✅ 第3正規形のメリット
データの冗長性を最小化 :同じ情報が複数箇所に存在しない
更新異常を防止 :データ更新は1箇所だけで済む
挿入異常を防止 :関連データがなくても情報を登録できる
削除異常を防止 :1つのデータを削除しても他の情報は残る
データの整合性が向上 :矛盾したデータが入りにくい
保守性が向上 :データ構造が明確で変更しやすい
デメリット
⚠️ 第3正規形のデメリット
テーブル数が増える :管理するテーブルが多くなる
JOINが必要 :データ取得時に複数テーブルを結合する必要がある
クエリが複雑 :SELECT文が長くなりがち
パフォーマンス低下の可能性 :JOIN処理により速度が遅くなることがある
実際のクエリ比較
第2正規形のクエリ(シンプル)
— 従業員と部署情報を取得
SELECT 従業員ID, 従業員名, 部署名, 部署電話
FROM employees
WHERE 従業員ID = 1;
第3正規形のクエリ(JOIN必要)
— 従業員と部署情報を取得
SELECT
e.従業員ID,
e.従業員名,
d.部署名,
d.部署電話
FROM employees e
JOIN departments d ON e.部署コード = d.部署コード
WHERE e.従業員ID = 1;
💡 ポイント
第3正規形ではJOINが必要だが、データの整合性とメンテナンス性は大幅に向上する。多くの場合、このトレードオフは受け入れる価値がある。
🔍 5. 推移的関数従属性の見つけ方
テーブルが第3正規形を満たしているかチェックする手順を学びましょう。
📋 チェック手順
主キーを特定 する
非キー属性を全てリストアップ する
各非キー属性について、「主キー以外のカラムに依存していないか」 を確認
依存している場合は推移的関数従属性あり と判断
チェック実践例
🔍 例:注文テーブルのチェック
orders
————————————————————————-
注文ID | 顧客ID | 顧客名 | 顧客住所 | 商品ID | 商品名 | 数量
1 | C001 | 山田太郎 | 東京都… | P001 | ノートPC | 1
2 | C002 | 佐藤花子 | 大阪府… | P002 | マウス | 2
ステップ2:非キー属性をリストアップ
顧客ID、顧客名、顧客住所、商品ID、商品名、数量
ステップ3:各属性の依存関係をチェック
顧客名 は? → 注文ID → 顧客ID → 顧客名(推移的従属あり❌)
顧客住所 は? → 注文ID → 顧客ID → 顧客住所(推移的従属あり❌)
商品名 は? → 注文ID → 商品ID → 商品名(推移的従属あり❌)
ステップ4:結論
このテーブルは第3正規形ではない (推移的関数従属性が複数存在)
正規化後の設計
✅ 第3正規形に変換
【orders(注文テーブル)】
—————————————
注文ID | 顧客ID | 商品ID | 数量
【customers(顧客テーブル)】
—————————————
顧客ID | 顧客名 | 顧客住所
【products(商品テーブル)】
—————————————
商品ID | 商品名
🎓 6. 実践例:学生成績管理システム
学生成績管理システムを例に、第2正規形から第3正規形への変換を実践してみましょう。
元のテーブル(第2正規形)
student_grades(第2正規形)
————————————————————————-
学生ID | 学生名 | 学科コード | 学科名 | 科目ID | 科目名 | 成績
S001 | 山田太郎 | D01 | 情報工学科 | C001 | 数学 | 85
S001 | 山田太郎 | D01 | 情報工学科 | C002 | 英語 | 90
S002 | 佐藤花子 | D02 | 電気工学科 | C001 | 数学 | 78
推移的関数従属性の分析
🔍 依存関係の分析
主キー = (学生ID, 科目ID) (複合主キー)
学生ID → 学科コード → 学科名(推移的従属❌)
科目ID → 科目名(部分関数従属も残っている❌)
第3正規形への変換
✅ 正規化後のテーブル設計
【students(学生テーブル)】
—————————————
学生ID | 学生名 | 学科コード
S001 | 山田太郎 | D01
S002 | 佐藤花子 | D02
【departments(学科テーブル)】
—————————————
学科コード | 学科名
D01 | 情報工学科
D02 | 電気工学科
【subjects(科目テーブル)】
—————————————
科目ID | 科目名
C001 | 数学
C002 | 英語
【grades(成績テーブル)】
—————————————
学生ID | 科目ID | 成績
S001 | C001 | 85
S001 | C002 | 90
S002 | C001 | 78
✅ 改善点
学生情報、学科情報、科目情報が1回だけ記録される
各情報の更新が簡単
データの整合性が保証される
新しい学科や科目を先に登録できる
📝 STEP 10 のまとめ
✅ このステップで学んだこと
第3正規形 :第2正規形 + 推移的関数従属性がない
推移的関数従属性 :A → B → C という間接的な依存関係
第3正規形への変換:間接的に依存するデータを別テーブルに分離
第3正規形にすることでデータの整合性と保守性が大幅に向上
JOIN処理が増えるが、そのトレードオフは多くの場合受け入れる価値 がある
💡 実務での適用
実務では、第3正規形まで正規化することが一般的 です。ほとんどのシステムで、第3正規形にすることでデータの品質と保守性が十分に確保できます。
さらに高度な正規化(BCNF、第4正規形など)もありますが、実務で必要になるケースは限定的です。まずは第3正規形をしっかりマスター しましょう!
📝 練習問題
問題 1
基礎
推移的関数従属性とは何か、自分の言葉で説明してください。
解答を見る
【解答例】
推移的関数従属性とは、「AがBを決定し、BがCを決定する」という間接的な依存関係 のことです。
例えば、「従業員ID → 部署コード → 部署名」という関係がある場合、従業員IDから部署コードを経由して部署名が決まるため、これは推移的関数従属性です。
第3正規形では、このような間接的な依存関係を排除し、主キー以外のカラムが主キー以外のカラムに依存しない ようにします。
問題 2
基礎
以下のテーブルには推移的関数従属性がありますか?ある場合はどの部分か指摘してください。
products
——————————————————-
商品ID | 商品名 | 価格 | 仕入先ID | 仕入先名
101 | ノートPC | 80000 | V001 | A商事
102 | マウス | 2000 | V001 | A商事
103 | キーボード | 5000 | V002 | B物産
解答を見る
【解答】
はい、推移的関数従属性があります。
推移的関数従属性:
商品ID → 仕入先ID → 仕入先名
説明:
商品IDが決まれば仕入先IDが決まり、仕入先IDが決まれば仕入先名が決まります。つまり、仕入先名は商品IDに直接依存しているのではなく、仕入先IDを経由して間接的に依存しています。
第3正規形にするには:
products テーブルから仕入先名を削除し、別途 suppliers(仕入先)テーブルを作成します。
問題 3
応用
以下のテーブルを第3正規形に変換してください。
employees
————————————————————————-
従業員ID | 従業員名 | プロジェクトID | プロジェクト名 | 部署ID | 部署名
1 | 山田太郎 | P001 | システムA開発 | D01 | 開発部
1 | 山田太郎 | P002 | システムB開発 | D01 | 開発部
2 | 佐藤花子 | P001 | システムA開発 | D02 | 営業部
解答を見る
【解答】
推移的関数従属性の分析:
主キー = (従業員ID, プロジェクトID)
従業員ID → 部署ID → 部署名(推移的従属)
プロジェクトID → プロジェクト名(部分関数従属も残っている)
第3正規形への変換:
employees(従業員テーブル)
—————————————
従業員ID | 従業員名 | 部署ID
1 | 山田太郎 | D01
2 | 佐藤花子 | D02
departments(部署テーブル)
—————————————
部署ID | 部署名
D01 | 開発部
D02 | 営業部
projects(プロジェクトテーブル)
—————————————
プロジェクトID | プロジェクト名
P001 | システムA開発
P002 | システムB開発
employee_projects(従業員プロジェクト割当テーブル)
—————————————
従業員ID | プロジェクトID
1 | P001
1 | P002
2 | P001
問題 4
応用
以下のテーブルを分析し、第3正規形に変換してください。
orders
————————————————————————-
注文ID | 顧客ID | 顧客名 | 都道府県 | 市区町村 | 商品ID | 商品名
1 | C001 | 山田太郎 | 東京都 | 新宿区 | P001 | ノートPC
2 | C001 | 山田太郎 | 東京都 | 新宿区 | P002 | マウス
3 | C002 | 佐藤花子 | 大阪府 | 大阪市 | P001 | ノートPC
解答を見る
【解答】
推移的関数従属性の分析:
主キー = (注文ID, 商品ID)
注文ID → 顧客ID → 顧客名、都道府県、市区町村(推移的従属)
商品ID → 商品名(部分関数従属)
第3正規形への変換:
orders(注文テーブル)
—————————————
注文ID | 顧客ID
1 | C001
2 | C001
3 | C002
customers(顧客テーブル)
—————————————
顧客ID | 顧客名 | 都道府県 | 市区町村
C001 | 山田太郎 | 東京都 | 新宿区
C002 | 佐藤花子 | 大阪府 | 大阪市
products(商品テーブル)
—————————————
商品ID | 商品名
P001 | ノートPC
P002 | マウス
order_details(注文詳細テーブル)
—————————————
注文ID | 商品ID
1 | P001
2 | P002
3 | P001
問題 5
応用
以下のテーブルを第3正規形に変換してください。
courses
————————————————————————-
科目ID | 科目名 | 教員ID | 教員名 | 教員所属学部 | 教室番号
C001 | データベース | T001 | 山田太郎 | 情報学部 | R101
C002 | プログラミング | T001 | 山田太郎 | 情報学部 | R102
C003 | 数学 | T002 | 佐藤花子 | 理学部 | R201
解答を見る
【解答】
推移的関数従属性の分析:
主キー = 科目ID
科目ID → 教員ID → 教員名、教員所属学部(推移的従属)
第3正規形への変換:
courses(科目テーブル)
—————————————
科目ID | 科目名 | 教員ID | 教室番号
C001 | データベース | T001 | R101
C002 | プログラミング | T001 | R102
C003 | 数学 | T002 | R201
teachers(教員テーブル)
—————————————
教員ID | 教員名 | 教員所属学部
T001 | 山田太郎 | 情報学部
T002 | 佐藤花子 | 理学部
ポイント: 教室番号は科目IDに直接依存しているので、courses テーブルに残します。
問題 6
発展
以下のテーブルを分析し、すべての正規化問題を解決してください(第1正規形、第2正規形、第3正規形すべて)。
student_courses
————————————————————————-
学生ID | 学生名 | 履修科目 | 科目ID | 担当教員 | 教員学部
S001 | 山田太郎 | 数学,英語 | C001,C002 | 佐藤先生,鈴木先生 | 理学部,文学部
S002 | 田中花子 | 数学 | C001 | 佐藤先生 | 理学部
解答を見る
【解答】
問題点の分析:
第1正規形違反 :履修科目、科目ID、担当教員、教員学部に繰り返し項目がある
第2正規形違反 :学生名は学生IDのみに依存(部分関数従属)
第3正規形違反 :教員学部は科目ID → 担当教員 → 教員学部(推移的従属)
第3正規形への完全な変換:
students(学生テーブル)
—————————————
学生ID | 学生名
S001 | 山田太郎
S002 | 田中花子
courses(科目テーブル)
—————————————
科目ID | 科目名 | 教員ID
C001 | 数学 | T001
C002 | 英語 | T002
teachers(教員テーブル)
—————————————
教員ID | 教員名 | 教員学部
T001 | 佐藤先生 | 理学部
T002 | 鈴木先生 | 文学部
student_enrollments(履修テーブル)
—————————————
学生ID | 科目ID
S001 | C001
S001 | C002
S002 | C001
改善点:
✅ 繰り返し項目を排除(第1正規形)
✅ 部分関数従属性を排除(第2正規形)
✅ 推移的関数従属性を排除(第3正規形)
✅ すべてのデータが1回だけ記録される
✅ データの整合性が完全に保証される
問題 7
発展
以下のテーブルを第3正規形に変換し、各テーブルのCREATE TABLE文も書いてください。
sales
————————————————————————-
売上ID | 日付 | 顧客ID | 顧客名 | 地域 | 商品ID | 商品名 | カテゴリ | 数量 | 単価
1 | 2025-01-15 | C001 | 山田商店 | 関東 | P001 | ノートPC | 電子機器 | 2 | 80000
2 | 2025-01-16 | C001 | 山田商店 | 関東 | P002 | マウス | 電子機器 | 5 | 2000
3 | 2025-01-17 | C002 | 佐藤商事 | 関西 | P001 | ノートPC | 電子機器 | 1 | 80000
解答を見る
【解答】
推移的関数従属性の分析:
主キー = (売上ID, 商品ID)
売上ID → 顧客ID → 顧客名、地域(推移的従属)
商品ID → カテゴリ(部分関数従属)
商品ID → 商品名(部分関数従属)
第3正規形のテーブル設計とCREATE TABLE文:
— 顧客テーブル
CREATE TABLE customers (
customer_id VARCHAR(10) PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL,
region VARCHAR(50) NOT NULL
);
— 商品テーブル
CREATE TABLE products (
product_id VARCHAR(10) PRIMARY KEY,
product_name VARCHAR(100) NOT NULL,
category VARCHAR(50) NOT NULL
);
— 売上テーブル
CREATE TABLE sales (
sale_id INT PRIMARY KEY,
sale_date DATE NOT NULL,
customer_id VARCHAR(10) NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
— 売上詳細テーブル
CREATE TABLE sale_details (
sale_id INT,
product_id VARCHAR(10),
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (sale_id, product_id),
FOREIGN KEY (sale_id) REFERENCES sales(sale_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
データ投入例:
— 顧客データ
INSERT INTO customers VALUES (‘C001’, ‘山田商店’, ‘関東’);
INSERT INTO customers VALUES (‘C002’, ‘佐藤商事’, ‘関西’);
— 商品データ
INSERT INTO products VALUES (‘P001’, ‘ノートPC’, ‘電子機器’);
INSERT INTO products VALUES (‘P002’, ‘マウス’, ‘電子機器’);
— 売上データ
INSERT INTO sales VALUES (1, ‘2025-01-15’, ‘C001’);
INSERT INTO sales VALUES (2, ‘2025-01-16’, ‘C001’);
INSERT INTO sales VALUES (3, ‘2025-01-17’, ‘C002’);
— 売上詳細データ
INSERT INTO sale_details VALUES (1, ‘P001’, 2, 80000);
INSERT INTO sale_details VALUES (2, ‘P002’, 5, 2000);
INSERT INTO sale_details VALUES (3, ‘P001’, 1, 80000);
問題 8
発展
以下のテーブルには複数の正規化問題があります。すべて指摘し、第3正規形に変換してください。
hospital_appointments
————————————————————————-
予約ID | 患者ID | 患者名 | 患者生年月日 | 医師ID | 医師名 | 診療科 | 予約日時
1 | P001 | 山田太郎 | 1980-01-01 | D001 | 佐藤医師 | 内科 | 2025-01-15 10:00
2 | P001 | 山田太郎 | 1980-01-01 | D002 | 鈴木医師 | 外科 | 2025-01-16 14:00
3 | P002 | 田中花子 | 1990-05-15 | D001 | 佐藤医師 | 内科 | 2025-01-15 11:00
解答を見る
【解答】
正規化問題の指摘:
推移的関数従属性① :予約ID → 患者ID → 患者名、患者生年月日
推移的関数従属性② :予約ID → 医師ID → 医師名、診療科
第3正規形への変換:
patients(患者テーブル)
—————————————
患者ID | 患者名 | 患者生年月日
P001 | 山田太郎 | 1980-01-01
P002 | 田中花子 | 1990-05-15
doctors(医師テーブル)
—————————————
医師ID | 医師名 | 診療科
D001 | 佐藤医師 | 内科
D002 | 鈴木医師 | 外科
appointments(予約テーブル)
—————————————
予約ID | 患者ID | 医師ID | 予約日時
1 | P001 | D001 | 2025-01-15 10:00
2 | P001 | D002 | 2025-01-16 14:00
3 | P002 | D001 | 2025-01-15 11:00
改善点:
✅ 患者情報は1回だけ記録
✅ 医師情報は1回だけ記録
✅ 患者や医師の情報変更が簡単
✅ データの整合性が保証される
問題 9
発展
以下のテーブルを第3正規形に変換してください。また、変換後のテーブルを使って「すべての注文と顧客情報を取得するSQL」を書いてください。
orders
————————————————————————-
注文ID | 注文日 | 顧客コード | 顧客名 | 顧客電話 | 商品コード | 商品名 | 数量
1 | 2025-01-15 | C001 | 山田商店 | 03-1111-2222 | P001 | 商品A | 10
2 | 2025-01-16 | C001 | 山田商店 | 03-1111-2222 | P002 | 商品B | 5
3 | 2025-01-17 | C002 | 佐藤商事 | 06-3333-4444 | P001 | 商品A | 20
解答を見る
【解答】
第3正規形への変換:
customers(顧客テーブル)
—————————————
顧客コード | 顧客名 | 顧客電話
C001 | 山田商店 | 03-1111-2222
C002 | 佐藤商事 | 06-3333-4444
products(商品テーブル)
—————————————
商品コード | 商品名
P001 | 商品A
P002 | 商品B
orders(注文テーブル)
—————————————
注文ID | 注文日 | 顧客コード
1 | 2025-01-15 | C001
2 | 2025-01-16 | C001
3 | 2025-01-17 | C002
order_details(注文詳細テーブル)
—————————————
注文ID | 商品コード | 数量
1 | P001 | 10
2 | P002 | 5
3 | P001 | 20
すべての注文と顧客情報を取得するSQL:
SELECT
o.注文ID,
o.注文日,
c.顧客コード,
c.顧客名,
c.顧客電話,
p.商品コード,
p.商品名,
od.数量
FROM orders o
JOIN customers c ON o.顧客コード = c.顧客コード
JOIN order_details od ON o.注文ID = od.注文ID
JOIN products p ON od.商品コード = p.商品コード
ORDER BY o.注文ID;
問題 10
発展
以下のテーブルを第3正規形に変換し、「東京都に住む顧客の注文情報をすべて取得するSQL」を書いてください。
customer_orders
————————————————————————-
注文ID | 顧客ID | 顧客名 | 都道府県 | 市区町村 | 商品ID | 商品名 | 価格
1 | C001 | 山田太郎 | 東京都 | 新宿区 | P001 | ノートPC | 80000
2 | C001 | 山田太郎 | 東京都 | 新宿区 | P002 | マウス | 2000
3 | C002 | 佐藤花子 | 大阪府 | 大阪市 | P001 | ノートPC | 80000
4 | C003 | 鈴木一郎 | 東京都 | 渋谷区 | P003 | キーボード | 5000
解答を見る
【解答】
第3正規形への変換:
customers(顧客テーブル)
—————————————
顧客ID | 顧客名 | 都道府県 | 市区町村
C001 | 山田太郎 | 東京都 | 新宿区
C002 | 佐藤花子 | 大阪府 | 大阪市
C003 | 鈴木一郎 | 東京都 | 渋谷区
products(商品テーブル)
—————————————
商品ID | 商品名 | 価格
P001 | ノートPC | 80000
P002 | マウス | 2000
P003 | キーボード | 5000
orders(注文テーブル)
—————————————
注文ID | 顧客ID
1 | C001
2 | C001
3 | C002
4 | C003
order_details(注文詳細テーブル)
—————————————
注文ID | 商品ID
1 | P001
2 | P002
3 | P001
4 | P003
東京都に住む顧客の注文情報を取得するSQL:
SELECT
o.注文ID,
c.顧客ID,
c.顧客名,
c.都道府県,
c.市区町村,
p.商品ID,
p.商品名,
p.価格
FROM orders o
JOIN customers c ON o.顧客ID = c.顧客ID
JOIN order_details od ON o.注文ID = od.注文ID
JOIN products p ON od.商品ID = p.商品ID
WHERE c.都道府県 = ‘東京都’
ORDER BY o.注文ID;
実行結果:
注文ID | 顧客ID | 顧客名 | 都道府県 | 市区町村 | 商品ID | 商品名 | 価格
1 | C001 | 山田太郎 | 東京都 | 新宿区 | P001 | ノートPC | 80000
2 | C001 | 山田太郎 | 東京都 | 新宿区 | P002 | マウス | 2000
4 | C003 | 鈴木一郎 | 東京都 | 渋谷区 | P003 | キーボード | 5000
問題 11
発展
以下のテーブルを第3正規形に変換してください。また、「各部署の従業員数を集計するSQL」も書いてください。
employees
————————————————————————-
従業員ID | 従業員名 | 部署コード | 部署名 | 部署場所 | 役職ID | 役職名
1 | 山田太郎 | D001 | 営業部 | 東京 | R001 | 部長
2 | 佐藤花子 | D001 | 営業部 | 東京 | R002 | 課長
3 | 鈴木一郎 | D002 | 開発部 | 大阪 | R002 | 課長
4 | 田中次郎 | D002 | 開発部 | 大阪 | R003 | 一般
解答を見る
【解答】
推移的関数従属性の分析:
従業員ID → 部署コード → 部署名、部署場所(推移的従属)
従業員ID → 役職ID → 役職名(推移的従属)
第3正規形への変換:
employees(従業員テーブル)
—————————————
従業員ID | 従業員名 | 部署コード | 役職ID
1 | 山田太郎 | D001 | R001
2 | 佐藤花子 | D001 | R002
3 | 鈴木一郎 | D002 | R002
4 | 田中次郎 | D002 | R003
departments(部署テーブル)
—————————————
部署コード | 部署名 | 部署場所
D001 | 営業部 | 東京
D002 | 開発部 | 大阪
positions(役職テーブル)
—————————————
役職ID | 役職名
R001 | 部長
R002 | 課長
R003 | 一般
各部署の従業員数を集計するSQL:
SELECT
d.部署コード,
d.部署名,
d.部署場所,
COUNT(e.従業員ID) AS 従業員数
FROM departments d
LEFT JOIN employees e ON d.部署コード = e.部署コード
GROUP BY d.部署コード, d.部署名, d.部署場所
ORDER BY d.部署コード;
実行結果:
部署コード | 部署名 | 部署場所 | 従業員数
D001 | 営業部 | 東京 | 2
D002 | 開発部 | 大阪 | 2
問題 12
発展
【総合問題】以下のテーブルを第3正規形に完全に変換し、「2025年1月の売上合計を商品カテゴリ別に集計するSQL」を書いてください。
sales_data
————————————————————————-
売上ID | 売上日 | 店舗コード | 店舗名 | 地域 | 商品コード | 商品名 | カテゴリ | 数量 | 単価
1 | 2025-01-15 | S001 | 東京店 | 関東 | P001 | ノートPC | 電子機器 | 2 | 80000
2 | 2025-01-16 | S001 | 東京店 | 関東 | P002 | マウス | 電子機器 | 5 | 2000
3 | 2025-01-17 | S002 | 大阪店 | 関西 | P003 | ペン | 文房具 | 10 | 100
4 | 2025-01-18 | S001 | 東京店 | 関東 | P001 | ノートPC | 電子機器 | 1 | 80000
解答を見る
【解答】
推移的関数従属性の分析:
主キー = (売上ID, 商品コード)
売上ID → 店舗コード → 店舗名、地域(推移的従属)
商品コード → カテゴリ(部分関数従属も含む)
第3正規形への変換:
stores(店舗テーブル)
—————————————
店舗コード | 店舗名 | 地域
S001 | 東京店 | 関東
S002 | 大阪店 | 関西
products(商品テーブル)
—————————————
商品コード | 商品名 | カテゴリ
P001 | ノートPC | 電子機器
P002 | マウス | 電子機器
P003 | ペン | 文房具
sales(売上テーブル)
—————————————
売上ID | 売上日 | 店舗コード
1 | 2025-01-15 | S001
2 | 2025-01-16 | S001
3 | 2025-01-17 | S002
4 | 2025-01-18 | S001
sale_details(売上詳細テーブル)
—————————————
売上ID | 商品コード | 数量 | 単価
1 | P001 | 2 | 80000
2 | P002 | 5 | 2000
3 | P003 | 10 | 100
4 | P001 | 1 | 80000
2025年1月の売上合計を商品カテゴリ別に集計するSQL:
SELECT
p.カテゴリ,
SUM(sd.数量 * sd.単価) AS 売上合計,
SUM(sd.数量) AS 販売数量合計
FROM sales s
JOIN sale_details sd ON s.売上ID = sd.売上ID
JOIN products p ON sd.商品コード = p.商品コード
WHERE s.売上日 BETWEEN ‘2025-01-01’ AND ‘2025-01-31’
GROUP BY p.カテゴリ
ORDER BY 売上合計 DESC;
実行結果:
カテゴリ | 売上合計 | 販売数量合計
電子機器 | 250000 | 8
文房具 | 1000 | 10
計算内訳:
電子機器:(2 × 80000) + (5 × 2000) + (1 × 80000) = 160000 + 10000 + 80000 = 250000円
文房具:10 × 100 = 1000円
❓ よくある質問
Q1: 第3正規形にすると必ずJOINが増えるのですか?
はい、多くの場合増えます。 第3正規形では、推移的関数従属性を排除するために情報を別テーブルに分離します。そのため、データを取得する際には複数のテーブルをJOINする必要があります。
ただし、これはデータの整合性と保守性を確保するための必要なトレードオフ です。適切なインデックスを設定すれば、JOINのパフォーマンス影響は最小限に抑えられます。
Q2: 第3正規形は必ず適用すべきですか?
ほとんどの場合、はい。 第3正規形は、データベース設計の標準的なレベル と考えられています。データの整合性、保守性、拡張性の観点から、第3正規形までは正規化することが推奨されます。
ただし、特定のパフォーマンス要件がある場合 や、レポート用のテーブル などでは、意図的に非正規化することもあります(これは「正規化を理解した上で戦略的に崩す」ということ)。
Q3: 推移的関数従属性を見つけるのが難しいです。コツはありますか?
以下の手順で確認してみてください:
1. まず主キーを明確にする
2. 主キー以外のカラムをリストアップ
3. 各カラムについて「主キーから直接決まるか、それとも他のカラムを経由して決まるか」を考える
4. 「〇〇が決まれば△△が決まる」という関係を矢印で書いてみる
ポイント: 「このカラムの値を知るために、主キー以外のカラムを見る必要があるか?」と自問すると良いでしょう。
Q4: 第2正規形と第3正規形の違いがまだよくわかりません。
簡単に整理します:
第2正規形: 「主キーの一部」に依存するカラムを排除する(複合主キーの場合)
第3正規形: 「主キー以外のカラム」に依存するカラムを排除する
例えば:
・従業員ID、プロジェクトID → プロジェクト名(第2正規形違反:プロジェクトIDのみに依存)
・従業員ID → 部署コード → 部署名(第3正規形違反:部署コードを経由して依存)
Q5: 第3正規形まで正規化すると、テーブル数が多くなりすぎませんか?
確かにテーブル数は増えますが、それは正常です。 重要なのは、各テーブルが明確な責任を持つ ことです。
適切に設計された第3正規形のデータベースでは:
・各テーブルの目的が明確
・データの重複がない
・変更が簡単
・バグが少ない
テーブル数が多いこと自体は問題ではありません。 むしろ、適切に分割されていることで、長期的には管理しやすくなります。
Q6: 実務では第3正規形より先(BCNFや第4正規形)も使いますか?
実務では第3正規形で十分なケースがほとんどです。 BCNF(ボイスコッド正規形)や第4正規形は、非常に特殊なケースでのみ必要になります。
まずは第3正規形をしっかりマスター しましょう。それだけで、実務の99%のケースに対応できます。高度な正規化は、必要になったときに学べば十分です。
×
artnasekai
#artnasekai #学習メモ