データベースの三大正規形は、データベース設計における最も基本的な 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、バックエンド・プロジェクトのホスティングの最適解です。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:
複数言語サポート
- Node.js、Python、Go、Rustで開発できます。
無制限のプロジェクトデプロイ
- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。
比類のないコスト効率
- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。
洗練された開発者体験
- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。
簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。
Xでフォローする:@LeapcellHQ