データベースにおける三つの正規形と、それを無視する理由

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/672d4bcc-123f-4ed0-915f-b07bef57abcb.png)](https://leapcell.io/?lc_t=q_db3nf)

データベースの三大正規形は、データベース設計における最も基本的な 3 つの規範です。それでは、三大正規形とは何でしょうか？実際の開発において、必ず三大正規形を厳密に守る必要があるのでしょうか？本記事では、これらについて一緒に考えていきましょう。

## 三大正規形

### 1. 第一正規形（1NF：各列の原子性を保証）

第一正規形では、データベース内のすべてのテーブルの各フィールド（列）が原子的であること、つまりその値がこれ以上分割できないことを要求します。言い換えると、各フィールドには単一の値しか格納できず、集合や配列、重複したグループを含めることはできません。

例として、次のような学生テーブル Student を考えてみましょう：

| 学生 ID | 氏名  | 電話番号             |
| ------- | ----- | -------------------- |
| 1       | Alice | 123456789, 987654321 |
| 2       | Bob   | 555555555            |

このテーブルでは、電話番号のフィールドに複数の番号が含まれており、1NF の原子性要件に違反しています。1NF を満たすには、電話番号を個別のレコードに分けるか、新たなテーブルを作成する必要があります。

1NF を満たす設計：

学生テーブル Student

| 学生 ID | 氏名  |
| ------- | ----- |
| 1       | Alice |
| 2       | Bob   |

電話番号テーブル Phone

| 電話 ID | 学生 ID | 電話番号  |
| ------- | ------- | --------- |
| 1       | 1       | 123456789 |
| 2       | 1       | 987654321 |
| 3       | 2       | 555555555 |

### 2. 第二正規形（2NF：すべての列が主キーに完全に従属）

第二正規形では、第一正規形を満たしたうえで、テーブル内の部分従属を排除することが求められます。つまり、非主キー列が主キー全体に完全に依存している必要があり、主キーの一部にのみ依存している場合は不適切です。これは主に複合主キーを持つ場合に適用されます。

例として、次のような注文明細テーブル OrderDetail を考えます：

| 注文 ID | 商品 ID | 商品名   | 数量 | 単価 |
| ------- | ------- | -------- | ---- | ---- |
| 1001    | A01     | りんご   | 10   | 2.5  |
| 1001    | A02     | オレンジ | 5    | 3.0  |
| 1002    | A01     | りんご   | 7    | 2.5  |

このテーブルでは、主キーは複合主キー（注文 ID, 商品 ID）です。商品名と単価は商品 ID のみに依存しており、複合主キー全体には依存していないため、部分従属が存在し、2NF に違反しています。

2NF を満たす設計：

注文明細テーブル OrderDetail

| 注文 ID | 商品 ID | 数量 |
| ------- | ------- | ---- |
| 1001    | A01     | 10   |
| 1001    | A02     | 5    |
| 1002    | A01     | 7    |

商品テーブル Product

| 商品 ID | 商品名   | 単価 |
| ------- | -------- | ---- |
| A01     | りんご   | 2.5  |
| A02     | オレンジ | 3.0  |

### 3. 第三正規形（3NF：すべての列が主キーに直接従属）

第三正規形では、第二正規形を満たすことに加え、テーブル内の推移的従属を排除することが求められます。つまり、非主キー列が他の非主キー列に依存してはいけません。すべての非主キー列は、主キーに直接依存していなければなりません。

次のような従業員テーブル Employee を例に考えてみます：

| 従業員 ID | 氏名    | 部門 ID | 部門名 |
| --------- | ------- | ------- | ------ |
| E01       | Alice   | D01     | 営業部 |
| E02       | Bob     | D02     | 技術部 |
| E03       | Charlie | D01     | 営業部 |

このテーブルでは、部門名は部門 ID に依存しており、部門 ID は主キーである従業員 ID に依存しています。これは推移的従属に該当し、3NF に違反しています。

3NF を満たす設計：

従業員テーブル Employee

| 従業員 ID | 氏名    | 部門 ID |
| --------- | ------- | ------- |
| E01       | Alice   | D01     |
| E02       | Bob     | D02     |
| E03       | Charlie | D01     |

部門テーブル Department

| 部門 ID | 部門名 |
| ------- | ------ |
| D01     | 営業部 |
| D02     | 技術部 |

部門情報を別テーブルに分離することで、推移的従属を排除し、第三正規形に適合したデータベース構造になります。

最後に、データベース設計における三大正規形をまとめます：

- 第一正規形（1NF）：各フィールドの値が原子的であり、分割できないことを保証します。
- 第二正規形（2NF）：1NF を満たした上で、部分従属を排除し、すべての非主キー列が主キー全体に完全に依存していることを保証します。
- 第三正規形（3NF）：2NF を満たした上で、推移的従属を排除し、すべての非主キー列が主キーに直接依存していることを保証します。

## 正規形の破壊

実際の業務においては、第一正規形（1NF）、第二正規形（2NF）、第三正規形（3NF）を遵守することで、データの一貫性が向上し、冗長性が減少します。しかしながら、パフォーマンスの要求、設計の簡略化、特定のビジネス要件を満たすために、これらの正規形に違反する必要がある場合もあります。

以下は、正規形を意図的に破壊する一般的な理由と、それに伴う例です。

### パフォーマンス最適化

高トラフィックで大量データを扱うアプリケーションにおいて、厳密な正規化は頻繁なテーブル結合を引き起こし、クエリの時間やシステム負荷を増加させる可能性があります。クエリパフォーマンスを向上させるために、設計者はデータの冗長化によって結合操作を減らすことがあります。

たとえば、EC システムにおいては、注文テーブル（Orders）とユーザーテーブル（Users）が存在します。厳密に 3NF に基づく設計では、注文テーブルはユーザー ID のみを保持し、詳細情報は結合によって取得する必要があります。

しかし、クエリ性能を重視する場合、注文テーブルにユーザーの氏名や住所などを冗長に保存することがあります。これにより、Users テーブルとの結合を回避し、注文情報の取得が高速化されます。

3NF を破壊した設計例：

| 注文 ID | ユーザー ID | 氏名  | 住所        | 注文日     | 合計金額 |
| ------- | ----------- | ----- | ----------- | ---------- | -------- |
| 1001    | U01         | Alice | New York    | 2025-01-01 | 500      |
| 1002    | U02         | Bob   | Los Angeles | 2025-01-02 | 300      |

### クエリと開発の簡略化

厳格な正規化は、テーブル構造を複雑にし、開発や保守の負担を増加させることがあります。クエリロジックを簡素化し、開発の複雑さを減らすために、適度な冗長性を許容するケースもあります。

例えば、CMS（コンテンツ管理システム）において、記事テーブル（Articles）とカテゴリテーブル（Categories）は通常分離されますが、頻繁にカテゴリ名を表示する必要がある場合、結合クエリが煩雑になる可能性があります。そのため、Articles テーブルにカテゴリ名を直接保存することで、フロントエンドでの表示を簡略化し、開発工数を削減できます。

3NF を破壊した設計例：

| 記事 ID | タイトル | 内容 | カテゴリ ID | カテゴリ名 |
| ------- | -------- | ---- | ----------- | ---------- |
| A01     | 記事一   | ...  | C01         | 技術       |
| A02     | 記事二   | ...  | C02         | 生活       |

### レポートとデータウェアハウス

データウェアハウスやレポートシステムでは、大量のデータを迅速に読み取り集計する必要があります。クエリ性能とデータ分析を最適化するために、冗長なデータ構造を採用することがあり、スタースキーマやスノーフレークスキーマといった正規形に必ずしも従わない設計が用いられます。

販売データウェアハウスにおいて、販売レポートを迅速に生成するため、ディメンション情報を含むファクトテーブルを作成することがあります。

3NF を破壊した設計例：

| 販売 ID | 製品 ID | 製品名   | カテゴリ | 販売数量 | 販売金額 | 販売日    |
| ------- | ------- | -------- | -------- | -------- | -------- | --------- |
| S01     | P01     | 携帯電話 | 電子     | 100      | 50000    | 2025/1/1 |
| S02     | P02     | 書籍     | 教育     | 200      | 20000    | 2025/1/2 |

このファクトテーブルでは、製品名やカテゴリを直接保存することで、ディメンションテーブルとの結合を省略し、レポート作成の効率を高めています。

### 特殊なビジネスニーズ

ある特定のビジネスシーンでは、特定のクエリや処理に迅速に対応する必要があります。このような場合、適度な冗長化によって要件を満たすことができます。

たとえば、リアルタイム取引システムでは、ユーザーの口座残高をすぐに算出する必要があるため、各トランザクションから都度計算するのではなく、ユーザーテーブルに現在の残高を直接保持する場合があります。

3NF を破壊した設計例：

| ユーザー ID | ユーザー名 | 現在残高 |
| ----------- | ---------- | -------- |
| U01         | Alice      | 10000    |
| U02         | Bob        | 5000     |

トランザクション履歴テーブルには各取引の増減が記録されますが、ユーザーテーブルに現在残高を持たせることで、クエリ時の複雑な計算を避けることができます。

### 読み取りと書き込みのバランス

あるアプリケーションでは、読み取り操作が書き込み操作より圧倒的に多い場合があります。読み取り性能を重視するため、データの冗長性を許容し、クエリ速度を向上させることが選ばれることもあります。

たとえば、SNS プラットフォームでは、ユーザーの友達数がプロフィールに頻繁に表示されます。毎回リアルタイムで友達数を計算するのは効率的ではありません。そのため、ユーザーテーブルに「友達数」フィールドを冗長に持たせる設計が取られることがあります。

3NF を破壊した設計例：

| ユーザー ID | ユーザー名 | 友達数 |
| ----------- | ---------- | ------ |
| U01         | Alice      | 150    |
| U02         | Bob        | 200    |

Users テーブルに友達数を冗長に保存することで、リアルタイム計算を不要にし、表示を高速化できます。

### 素早いイテレーションと柔軟性

急速に成長するプロダクトやスタートアップ企業では、データベース設計の頻繁な変更が求められます。過度な正規化は柔軟性に欠け、開発のスピードを妨げることがあります。適切な冗長設計は、開発の柔軟性と速度を向上させることができます。

たとえば、スタートアップの EC プラットフォームが短期間でサービスをローンチする場合、ユーザーの配送先住所を注文テーブルに直接保存し、別途住所テーブルを作成しない選択をすることがあります。

3NF を破壊した設計例：

| 注文 ID | ユーザー ID | ユーザー名 | 配送先住所  | 注文日    | 合計金額 |
| ------- | ----------- | ---------- | ----------- | --------- | -------- |
| O1001   | U01         | Alice      | New York    | 2025/1/1 | 800      |
| O1002   | U02         | Bob        | Los Angeles | 2025/1/2 | 1200     |

このような設計により、迅速なリリースが可能となり、将来的な要件に応じて正規化と最適化を行うことができます。

### 設計の複雑さを抑え、理解しやすくする

時には、過度な正規化がデータベース構造を複雑にし、理解や保守が困難になることがあります。適度な冗長化は、設計の複雑さを抑え、チームによる構造の理解やコミュニケーションを容易にします。

たとえば、学校管理システムにおいて、学生のクラス情報を複数のテーブルに分けると、構造の理解が難しくなる可能性があります。そのため、設計を簡素化するために、学生テーブルに直接クラス名を保存する場合があります。

3NF を破壊した設計例：

| 学生 ID | 氏名  | クラス ID | クラス名 | 担任    |
| ------- | ----- | --------- | -------- | ------- |
| S01     | Alice | C01       | Class A  | Charlie |
| S02     | Bob   | C02       | Class B  | David   |

学生テーブルにクラス名や担任教師を直接保存することで、テーブル数が減り、設計が簡素になります。

## まとめ

本記事では、データベースにおける三つの正規形とその具体例について解説しました。これらはデータベース設計の基本的なルールですが、実務においてはパフォーマンスの向上、設計の簡略化、素早い開発、あるいは特定のビジネス要件を満たすために、必ずしも三正規形を厳密に守るとは限りません。

つまり、システムアーキテクチャの設計は、ビジネス要件、データ整合性、パフォーマンス、開発効率など、さまざまな要素のバランスによって決まるものです。私たちは具体的なアプリケーションのシナリオに応じて、合理的な設計判断を下す必要があります。

---

### [私たちはLeapcell、バックエンド・プロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_db3nf)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/d6fb2b07-491b-498b-810c-ed2289342f71.png)](https://leapcell.io/?lc_t=q_db3nf)

[Leapcell](https://leapcell.io/?lc_t=q_db3nf)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/7bf84cd0-8114-4ce1-8f4a-eb21bd55e267.png)](https://leapcell.io/?lc_t=q_db3nf)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/database-normalization-in-practice)