STEP 9:第2正規形(2NF)

2️⃣ STEP 9: 第2正規形(2NF)

部分関数従属性を排除して、データの冗長性をさらに減らそう

📋 このステップで学ぶこと
  • 第2正規形(2NF)の定義と条件
  • 関数従属性とは何か
  • 部分関数従属性の問題点
  • 第1正規形から第2正規形への変換
  • 実践演習:第2正規形への正規化

学習時間の目安: 2.5時間 | 前提知識: STEP 1-8修了

🎯 1. 第2正規形(2NF)とは?

STEP 8からの続き:正規化の第二歩

STEP 8では、第1正規形(繰り返し項目の排除、アトミック性の確保)を学びました。このSTEP 9では、第2正規形を学び、部分関数従属性を排除していきます。

第2正規形の定義

📝 第2正規形とは

第2正規形(Second Normal Form, 2NF)第1正規形を満たし、かつ部分関数従属性がない状態

わかりやすく言うと、「主キーの一部だけに依存するカラムをなくす」というルールです。

📝 具体例

注文詳細テーブルで主キーが「注文ID + 商品ID」の場合:
・「商品名」は「商品ID」だけで決まる(注文IDは不要)
→ これが「部分関数従属」で、第2正規形違反です。

第2正規形にするには、商品名を別テーブル(商品テーブル)に分離します。

第2正規形の条件

📋 第2正規形を満たす2つの条件
  1. 第1正規形を満たす:繰り返し項目なし、アトミック性、主キーあり
  2. 部分関数従属性がない:すべての非キー属性が、主キー全体に従属する
💡 重要なポイント

主キーが単一カラムの場合、自動的に第2正規形を満たします。
部分関数従属は、複合主キーの場合にのみ問題になります。

🔗 2. 関数従属性とは

関数従属性の基本

📝 関数従属性とは

関数従属性(Functional Dependency)「Aが決まればBも決まる」という関係

関数従属性の例

例1:商品ID → 商品名
商品ID = 1 なら、商品名 = ‘ノートPC’ (必ず決まる) 商品ID = 2 なら、商品名 = ‘マウス’ (必ず決まる) → 「商品ID」が決まれば「商品名」も決まる → 商品名は商品IDに「関数従属」している
例2:郵便番号 → 住所(市区町村まで)
郵便番号 = ‘150-0001’ なら、住所 = ‘東京都渋谷区神宮前’ (決まる) 郵便番号 = ‘100-0001’ なら、住所 = ‘東京都千代田区千代田’ (決まる) → 郵便番号が決まれば住所も決まる → 住所は郵便番号に「関数従属」している
例3:学籍番号 → 学生名
学籍番号 = ‘S001’ なら、学生名 = ‘山田太郎’ 学籍番号 = ‘S002’ なら、学生名 = ‘佐藤花子’ → 学籍番号が決まれば学生名も決まる

記法

A → B 読み方:「AがBを関数的に決定する」または「BはAに関数従属する」 例: 商品ID → 商品名 学籍番号 → 学生名 (注文ID, 商品ID) → 数量

⚠️ 3. 部分関数従属性とは

部分関数従属性の定義

📝 部分関数従属性とは

部分関数従属性(Partial Functional Dependency)=複合主キーの一部だけに従属している状態

部分関数従属がある例(第2正規形違反)

❌ 第2正規形違反テーブル
注文詳細テーブル(第1正規形だが、第2正規形違反) ————————————————————— 注文ID | 商品ID | 商品名 | 単価 | 数量 | 顧客名 (PK) | (PK) | | | | ————————————————————— 1 | 101 | ノートPC | 80000 | 1 | 山田太郎 1 | 102 | マウス | 2000 | 2 | 山田太郎 2 | 101 | ノートPC | 80000 | 1 | 佐藤花子 【複合主キー】 (注文ID, 商品ID) 【関数従属性】 (注文ID, 商品ID) → 数量 ✅ OK(主キー全体に従属) 商品ID → 商品名 ❌ 部分関数従属(主キーの一部だけ) 商品ID → 単価 ❌ 部分関数従属(主キーの一部だけ) 注文ID → 顧客名 ❌ 部分関数従属(主キーの一部だけ)
⚠️ 問題点
  • ❌ 商品名、単価が重複している(ノートPCが2回)
  • ❌ 顧客名も重複(山田太郎が2回)
  • ❌ 商品名を変更する場合、複数箇所を更新する必要(更新異常)
  • ❌ ストレージの無駄

完全関数従属性

📝 完全関数従属性とは

完全関数従属性(Full Functional Dependency)=主キー全体に従属している状態

✅ 完全関数従属の例
注文詳細テーブル ————————————————————— 注文ID | 商品ID | 数量 (PK) | (PK) | ————————————————————— 1 | 101 | 1 1 | 102 | 2 2 | 101 | 1 【複合主キー】 (注文ID, 商品ID) 【関数従属性】 (注文ID, 商品ID) → 数量 ✅ 完全関数従属 数量は、注文IDと商品IDの「両方」が決まらないと決まらない → 主キー全体に従属している → 部分関数従属ではない

🔄 4. 第1正規形から第2正規形への変換

変換ステップ

📋 第2正規形への変換手順
  1. 部分関数従属を特定:主キーの一部だけに従属するカラムを見つける
  2. 新しいテーブルを作成:部分関数従属するカラムを別テーブルに分離
  3. 元のテーブルから削除:分離したカラムを元のテーブルから削除
  4. 外部キーで関連付け:新しいテーブルとの関係を保つ

実例1:注文詳細テーブル

❌ 第1正規形(第2正規形違反)
注文詳細テーブル ————————————————————— 注文ID | 商品ID | 商品名 | 単価 | 数量 | 顧客名 (PK) | (PK) | | | | ————————————————————— 1 | 101 | ノートPC | 80000 | 1 | 山田太郎 1 | 102 | マウス | 2000 | 2 | 山田太郎 2 | 101 | ノートPC | 80000 | 1 | 佐藤花子 【部分関数従属】 商品ID → 商品名、単価 注文ID → 顧客名
✅ 第2正規形
【顧客テーブル】 ———————————– 顧客ID | 顧客名 1 | 山田太郎 2 | 佐藤花子 【商品テーブル】 ———————————– 商品ID | 商品名 | 単価 101 | ノートPC | 80000 102 | マウス | 2000 【注文テーブル】 ———————————– 注文ID | 顧客ID | 注文日 1 | 1 | 2025-01-15 2 | 2 | 2025-01-16 【注文詳細テーブル】 ———————————– 注文ID | 商品ID | 数量 (PK) | (PK) | 1 | 101 | 1 1 | 102 | 2 2 | 101 | 1
✅ 改善点
  • 商品名、単価の重複がなくなった
  • 顧客名の重複もなくなった
  • 更新異常が解消
  • ストレージ効率が向上

実例2:学生成績テーブル

❌ 第1正規形(第2正規形違反)
学生成績テーブル ————————————————————— 学籍番号 | 科目コード | 学生名 | 科目名 | 担当教員 | 成績 (PK) | (PK) | | | | ————————————————————— S001 | M101 | 山田太郎 | 数学 | 佐藤先生 | 85 S001 | E101 | 山田太郎 | 英語 | 鈴木先生 | 90 S002 | M101 | 佐藤花子 | 数学 | 佐藤先生 | 78 【部分関数従属】 学籍番号 → 学生名 科目コード → 科目名、担当教員
✅ 第2正規形
【学生テーブル】 ———————————– 学籍番号 | 学生名 S001 | 山田太郎 S002 | 佐藤花子 【科目テーブル】 ———————————– 科目コード | 科目名 | 担当教員 M101 | 数学 | 佐藤先生 E101 | 英語 | 鈴木先生 【成績テーブル】 ———————————– 学籍番号 | 科目コード | 成績 (PK) | (PK) | S001 | M101 | 85 S001 | E101 | 90 S002 | M101 | 78

💡 5. 第2正規形のポイント

主キーが単一カラムの場合

✅ 主キーが単一カラムなら自動的に第2正規形
ユーザーテーブル ———————————– ユーザーID | ユーザー名 | メール (PK) | | 【関数従属性】 ユーザーID → ユーザー名 ユーザーID → メール 主キーは「ユーザーID」の1つだけ → 「主キーの一部」は存在しない → 部分関数従属は起こりえない → 自動的に第2正規形を満たす

つまり:部分関数従属は、複合主キーの場合にのみ問題になります。

第2正規形にするメリット

1. 冗長性の削減

同じデータの重複がなくなり、ストレージ効率が向上。

2. 更新異常の解消

1箇所だけ更新すればよく、データの整合性が保たれる。

3. 保守性の向上

データ構造が整理され、理解しやすくなる。

📝 STEP 9 のまとめ

✅ このステップで学んだこと
  • 第2正規形:第1正規形 + 部分関数従属性がない
  • 関数従属性:「Aが決まればBも決まる」という関係(A → B)
  • 部分関数従属:複合主キーの一部だけに従属している状態(第2正規形違反)
  • 完全関数従属:主キー全体に従属している状態(第2正規形OK)
  • 第2正規形への変換:部分関数従属するカラムを別テーブルに分離
  • 主キーが単一カラムなら、自動的に第2正規形を満たす
💡 重要ポイント

第2正規形は、複合主キーを持つテーブルで重要です。

チェックポイント:
✅ 主キーは複合キーか?
✅ 主キーの一部だけで決まるカラムはないか?
✅ あれば別テーブルに分離

次のSTEP 10では、第3正規形を学び、推移的関数従属を排除していきます!

📝 練習問題

問題 1 基礎

第2正規形の定義を答えてください。

【解答】

第1正規形を満たし、かつ部分関数従属性がない状態

つまり、すべての非キー属性が主キー全体に従属する(主キーの一部だけに従属しない)。

問題 2 基礎

関数従属性「商品ID → 商品名」を日本語で説明してください。

【解答】

「商品IDが決まれば、商品名も決まる」または「商品名は商品IDに関数従属する」

例:商品ID = 1 なら、必ず商品名 = ‘ノートPC’ のように一意に決まる。

問題 3 基礎

主キーが単一カラム(例:user_id)の場合、部分関数従属は発生しますか?

【解答】

いいえ、発生しません。

部分関数従属は「主キーの一部だけに従属する」状態です。主キーが単一カラムの場合、「主キーの一部」は存在しないため、自動的に第2正規形を満たします。

問題 4 応用

以下のテーブルは第2正規形を満たしていますか?理由も答えてください。

注文詳細テーブル ————————————— 注文ID | 商品ID | 数量 | 単価 (PK) | (PK) | |
【解答】

いいえ、第2正規形を満たしていません。

「単価」は「商品ID」だけで決まります(注文IDは不要)。つまり部分関数従属です。
改善案:単価を商品テーブルに分離する。

問題 5 応用

以下のテーブルを第2正規形に変換してください。

受講テーブル(第1正規形) ————————————————————— 学生ID | 講座ID | 学生名 | 講座名 | 担当講師 | 受講日 (PK) | (PK) | | | |
【解答】
【学生テーブル】 学生ID | 学生名 【講座テーブル】 講座ID | 講座名 | 担当講師 【受講テーブル】 学生ID | 講座ID | 受講日 (PK) | (PK) |

学生ID → 学生名、講座ID → 講座名・担当講師の部分関数従属を排除。

問題 6 応用

以下の関数従属性のうち、部分関数従属はどれですか?(主キー:注文ID, 商品ID)
A. (注文ID, 商品ID) → 数量 B. 商品ID → 商品名 C. 注文ID → 顧客名

【解答】

B と C が部分関数従属

A:主キー全体に従属 → 完全関数従属
B:商品IDだけに従属 → 部分関数従属
C:注文IDだけに従属 → 部分関数従属

問題 7 発展

部分関数従属を残したまま(第2正規形にしない)とどんな問題が発生しますか?

【解答】

1. 更新異常:商品名を変更する場合、複数行を更新する必要があり、更新漏れでデータ矛盾が発生。

2. 挿入異常:新商品を登録したいが、まだ誰も注文していない場合、登録できない。

3. 削除異常:注文を削除すると、商品情報も一緒に消えてしまう。

問題 8 発展

以下の要件を満たす、第2正規形のテーブルを設計してください。

【要件】病院の診察記録
・患者が医師の診察を受ける
・診察日、診察内容を記録
・医師には専門科目がある

【解答例】
【患者テーブル】 患者ID | 患者名 | 生年月日 【医師テーブル】 医師ID | 医師名 | 専門科目 【診察記録テーブル】 診察ID | 患者ID | 医師ID | 診察日 | 診察内容

診察IDを主キー(単一カラム)にすることで、自動的に第2正規形を満たす。

問題 9 発展

以下のテーブルを第2正規形に変換し、「すべての受講記録と学生名・講座名を取得するSQL」を書いてください。

受講テーブル(第1正規形) ————————————————————— 学生ID | 講座ID | 学生名 | 講座名 | 成績 (PK) | (PK) | | | S001 | C001 | 山田太郎 | データベース | 85 S001 | C002 | 山田太郎 | プログラミング | 90 S002 | C001 | 佐藤花子 | データベース | 78
【解答】

第2正規形への変換:

【students(学生テーブル)】 学生ID | 学生名 S001 | 山田太郎 S002 | 佐藤花子 【courses(講座テーブル)】 講座ID | 講座名 C001 | データベース C002 | プログラミング 【enrollments(受講テーブル)】 学生ID | 講座ID | 成績 S001 | C001 | 85 S001 | C002 | 90 S002 | C001 | 78

SQLクエリ:

SELECT e.学生ID, s.学生名, e.講座ID, c.講座名, e.成績 FROM enrollments e JOIN students s ON e.学生ID = s.学生ID JOIN courses c ON e.講座ID = c.講座ID ORDER BY e.学生ID, e.講座ID;
問題 10 発展

問題9で作成したテーブルを使って、「成績が80点以上の受講記録を、学生名と講座名付きで取得するSQL」を書いてください。

【解答】
SELECT s.学生名, c.講座名, e.成績 FROM enrollments e JOIN students s ON e.学生ID = s.学生ID JOIN courses c ON e.講座ID = c.講座ID WHERE e.成績 >= 80 ORDER BY e.成績 DESC;

実行結果:

学生名 | 講座名 | 成績 山田太郎 | プログラミング | 90 山田太郎 | データベース | 85
問題 11 発展

問題9で作成したテーブルを使って、「各学生の平均成績を計算するSQL」を書いてください。

【解答】
SELECT s.学生ID, s.学生名, COUNT(e.講座ID) AS 受講数, AVG(e.成績) AS 平均成績 FROM students s LEFT JOIN enrollments e ON s.学生ID = e.学生ID GROUP BY s.学生ID, s.学生名 ORDER BY 平均成績 DESC;

実行結果:

学生ID | 学生名 | 受講数 | 平均成績 S001 | 山田太郎 | 2 | 87.5 S002 | 佐藤花子 | 1 | 78.0

ポイント:GROUP BYで学生ごとにグループ化し、AVGで平均成績を計算。LEFT JOINを使うことで、受講がない学生も含めることができる。

問題 12 発展

【総合問題】以下のテーブルを第2正規形に変換し、「各部署の売上合計を計算するSQL」を書いてください。

売上テーブル(第1正規形) ————————————————————— 売上ID | 営業ID | 営業名 | 部署名 | 商品ID | 商品名 | 金額 (PK) | (PK) | | | | | 1 | E001 | 山田太郎 | 営業部 | P001 | 商品A | 10000 2 | E001 | 山田太郎 | 営業部 | P002 | 商品B | 20000 3 | E002 | 佐藤花子 | 開発部 | P001 | 商品A | 15000
【解答】

第2正規形への変換:

【employees(営業担当テーブル)】 営業ID | 営業名 | 部署名 E001 | 山田太郎 | 営業部 E002 | 佐藤花子 | 開発部 【products(商品テーブル)】 商品ID | 商品名 P001 | 商品A P002 | 商品B 【sales(売上テーブル)】 売上ID | 営業ID | 商品ID | 金額 1 | E001 | P001 | 10000 2 | E001 | P002 | 20000 3 | E002 | P001 | 15000

各部署の売上合計SQL:

SELECT e.部署名, COUNT(s.売上ID) AS 売上件数, SUM(s.金額) AS 売上合計 FROM employees e JOIN sales s ON e.営業ID = s.営業ID GROUP BY e.部署名 ORDER BY 売上合計 DESC;

実行結果:

部署名 | 売上件数 | 売上合計 営業部 | 2 | 30000 開発部 | 1 | 15000

ポイント:
✅ 部分関数従属(営業ID → 営業名・部署名、商品ID → 商品名)を排除
✅ GROUP BYとSUMで部署別集計
✅ JOINで必要な情報を結合

❓ よくある質問

Q1: 主キーが単一カラムなら、常に第2正規形を満たしますか?

はい、部分関数従属に関しては満たします。主キーが単一カラムの場合、「主キーの一部」は存在しないため、部分関数従属は起こりえません。ただし、第3正規形(推移的関数従属)の問題は残る可能性があります。

Q2: 複合主キーを避ければ、第2正規形を気にしなくていいですか?

はい、部分関数従属に関してはそうです。実務では、複合主キーを避けてサロゲートキー(ID)を主キーにすることが多いです。これにより自動的に第2正規形を満たします。

Q3: 第2正規形にすると、必ずパフォーマンスが悪くなりますか?

いいえ、必ずしもそうではありません。テーブルが小さくなりインデックスが効きやすくなる、更新が速くなるなどのメリットがあります。JOINが増えるデメリットはありますが、適切なインデックスを貼れば多くの場合は問題ありません。

Q4: 第1正規形と第2正規形、どちらが重要ですか?

両方とも重要ですが、第1正規形が最優先です。第1正規形がないと基本的なクエリすら書けません。第2正規形はデータの冗長性を減らす次のステップです。

Q5: 部分関数従属を見つけるコツはありますか?

「複合主キーの片方だけで決まるものはないか?」と自問してください。例えば、主キーが(注文ID, 商品ID)の場合、「商品IDだけで決まる値はないか?」「注文IDだけで決まる値はないか?」をチェックします。「商品名は商品IDだけで決まる」「顧客名は注文IDだけで決まる」などが典型的な部分関数従属です。

Q6: 正規化後、JOINでデータを取得するのが面倒です。良い方法はありますか?

ビュー(VIEW)を活用しましょう。よく使うJOINクエリをビューとして定義しておくと、あたかも1つのテーブルのように扱えます。また、ORM(Object-Relational Mapping)を使うと、プログラムからはJOINを意識せずにデータを取得できます。正規化されたテーブル構造を維持しつつ、利便性を確保する方法です。

📝

学習メモ

データベース設計・データモデリング - Step 9

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE