現代のあらゆるアプリケーションやサービスの根幹には、データを格納し、効率的に管理するためのデータベースが存在します。そして、そのデータベースが長期的に健全な状態を保ち、矛盾なくデータを扱えるかどうかは、その「設計」の質に大きく依存します。特にリレーショナルデータベース(RDB)の世界において、その設計品質を測るための最も重要で普遍的な概念が「正規化(Normalization)」です。多くの開発者がその名前を知り、基本的なルールを学んだ経験があるでしょう。しかし、正規化は単なる一連のルールや手順の暗記ではありません。それは、データの本質的な関係性を見抜き、データの冗長性を排除し、整合性を維持するための体系的な哲学であり、科学です。この文章では、正規化がなぜ必要なのかという根本的な問いから始め、第一正規形、第二正規形、第三正規形といった各段階を、具体的な例と共に深く掘り下げていきます。さらに、正規化の理論的支柱である「関数従属性」の概念を解き明かし、より高度な正規形にも触れながら、最後にはパフォーマンスという現実的な要求との間でどうバランスを取るべきか、つまり「非正規化」という戦略についても考察します。これは、データベース設計に関わるすべてのエンジニアにとって、避けては通れない知識の旅路となるでしょう。
なぜ正規化は不可欠なのか データが抱える「異常」の問題
正規化の具体的な手法に入る前に、まず「なぜそれが必要なのか」という動機を理解することが極めて重要です。正規化されていないデータベース、あるいは不適切に設計されたデータベースは、「データ異常(Data Anomaly)」と呼ばれる深刻な問題を引き起こす温床となります。データ異常は、大きく分けて以下の3種類が存在します。
- 更新異常(Update Anomaly): 同じ情報が複数の場所に重複して格納されているために発生します。例えば、ある顧客の住所が複数の注文レコードに記録されているとします。もしその顧客が引っ越した場合、関連するすべての注文レコードの住所を一つ残らず更新しなければなりません。もし一つでも更新を忘れてしまうと、データベース内には同じ顧客に対して新旧の住所が混在するという矛盾した状態、つまりデータの不整合が発生します。これは、データの信頼性を著しく損ないます。
- 挿入異常(Insertion Anomaly): 本来は独立して存在するべきデータを、別のデータと無理に関連付けているために発生します。例えば、商品情報とその商品を注文した顧客情報を一つのテーブルで管理しているとします。この設計では、まだ誰からも注文されていない新商品をシステムに登録しようとしても、注文情報(顧客IDなど)が存在しないためレコードを挿入できない、という事態が起こり得ます。商品の登録という本来可能なはずの操作が、別のデータの存在に依存してしまっているのです。
- 削除異常(Deletion Anomaly): 複数の異なる種類の情報が一つのレコードに集約されているために発生します。例えば、ある顧客が一度だけ商品を注文したとします。後にその注文がキャンセルになり、注文レコードを削除したとしましょう。もしそのテーブルに顧客情報(氏名や連絡先など)も含まれていた場合、注文の削除と同時に、その顧客に関する唯一の情報までシステムから失われてしまう可能性があります。これは、消すべきでない情報まで意図せず消してしまうという、極めて危険な問題です。
これらの異常は、アプリケーションのバグ、データの信頼性低下、そして開発者や運用者の精神的な負担増大に直結します。正規化とは、こうしたデータ異常の発生を構造的に防ぎ、データベースをより堅牢で、予測可能で、メンテナンスしやすい状態に導くための設計プロセスなのです。
正規化の心臓部 関数従属性という考え方
正規化の各ステップを正しく理解するためには、その根底に流れる「関数従属性(Functional Dependency)」という概念を把握することが不可欠です。これは少し数学的な響きがありますが、考え方は非常にシンプルです。ある属性(テーブルの列)の値が決まれば、別の属性の値が一意に決まるとき、後者は前者に「関数従属している」と言います。これを `X -> Y` と表記し、「XがYを決定する」と読みます。
例えば、「社員テーブル」を考えてみましょう。このテーブルには「社員ID」と「社員名」という列があるとします。この場合、「社員ID」が '101' と決まれば、「社員名」は「田中太郎」のように一意に定まります。逆は必ずしも真ではありません(同姓同名の社員がいる可能性があるため、「田中太郎」から「社員ID」が一意に決まるとは限りません)。この関係を、関数従属性を用いて `社員ID -> 社員名` と表現します。ここで、決定する側(社員ID)を決定子(Determinant)、決定される側(社員名)を従属先(Dependent)と呼びます。
+--------------+ +--------------+
| 社員ID | ------> | 社員名 |
+--------------+ +--------------+
(決定子) (従属先)
正規化のプロセスは、この関数従属性を注意深く分析し、「あるべきではない従属関係」を見つけ出し、テーブルを分割することでそれらを解消していく作業に他なりません。主キー(Primary Key)がテーブル内の他のすべての列を関数従属させている状態(`主キー -> 全ての非キー属性`)が、理想的な姿の一つです。これから見ていく第一正規形から第三正規形までのルールは、すべてこの関数従属性の考え方をベースに構築されています。
ステップ・バイ・ステップで進める正規化の旅
それでは、具体的な正規化のプロセスを段階的に見ていきましょう。正規化は通常、第一正規形(1NF)、第二正規形(2NF)、第三正規形(3NF)の順に進められます。前の段階の条件を満たさなければ、次の段階に進むことはできません。
第一正規形(1NF: First Normal Form) 「繰り返し」をなくす
第一正規形のルールは、正規化の出発点であり、最も基本的な要件です。
定義:テーブルの各セル(行と列が交差する場所)には、単一の値(スカラ値、アトミックな値)しか含まれてはならない。
これはつまり、「一つのセルにカンマ区切りで複数の値を入れる」や「繰り返し項目を持つ」といった設計を許さない、ということです。リレーショナルデータベースの思想の根幹をなすルールと言えます。
例えば、以下のような注文管理テーブルを考えてみましょう。このテーブルでは、1回の注文で複数の商品を購入した場合、`購入商品` 列にカンマ区切りで商品名を羅列しています。
【非正規形(0NF)のテーブル例】
注文テーブル +--------+------------+--------------------------+ | 注文ID | 注文日 | 購入商品 | +--------+------------+--------------------------+ | 1001 | 2023-04-01 | リンゴ, バナナ | | 1002 | 2023-04-02 | 牛乳 | | 1003 | 2023-04-03 | パン, 牛乳, 卵 | +--------+------------+--------------------------+
この設計には多くの問題が潜んでいます。
- 「牛乳」が含まれる注文だけを検索したい場合、`LIKE '%牛乳%'` のような文字列の部分一致検索が必要になり、非効率で間違いやすいです。
- どの商品が一番人気かを集計する際、`購入商品` 列の文字列をプログラムで分割して数える必要があり、データベースの能力を活かせません。
- 新しい商品「オレンジ」を追加しても、それが注文されるまでデータとして表現する場所がありません。
これを第一正規形にするには、繰り返し部分を独立した行として分解します。つまり、1つの注文に3つの商品が含まれているなら、それは3つの行として表現されるべきです。主キーは `注文ID` だけでは一意性を保てなくなるため、`注文ID` と `商品名` の組み合わせ(複合主キー)などが考えられます。
【第一正規形(1NF)に修正したテーブル例】
注文詳細テーブル +--------+------------+----------+ | 注文ID | 注文日 | 商品名 | +--------+------------+----------+ | 1001 | 2023-04-01 | リンゴ | | 1001 | 2023-04-01 | バナナ | | 1002 | 2023-04-02 | 牛乳 | | 1003 | 2023-04-03 | パン | | 1003 | 2023-04-03 | 牛乳 | | 1003 | 2023-04-03 | 卵 | +--------+------------+----------+
この修正により、各セルには単一の値しか含まれなくなりました。これにより、特定の商品を含む注文の検索や集計が格段に容易になり、データが構造化されたことがわかります。しかし、このテーブルにはまだ別の問題が隠されています。`注文日` の情報が、同じ注文ID '1001' や '1003' に対して重複してしまっています。これが次の第二正規形で解決すべき課題となります。
第二正規形(2NF: Second Normal Form) 部分関数従属性をなくす
第二正規形は、第一正規形であることが前提条件です。その上で、複合主キー(複数の列を組み合わせて主キーとしたもの)を持つテーブルにおける問題点に対処します。
定義:第一正規形であり、かつ、主キーを構成する一部の列に対してのみ関数従属する非キー属性(主キー以外の列)が存在しないこと。
これを「部分関数従属性(Partial Functional Dependency)を解消する」と言います。簡単に言えば、「すべての非キー属性は、主キー全体に対して完全に従属していなければならない」ということです。このルールは、主キーが単一の列で構成されている場合には自動的に満たされるため、主に複合主キーを持つテーブルを対象に考えます。
先ほどの第一正規形の例を少し拡張して、商品の単価や数量も管理するテーブルを考えてみましょう。主キーは `(注文ID, 商品ID)` の複合主キーとします。
【第一正規形だが第二正規形ではないテーブル例】
注文明細テーブル +--------+----------+----------+--------+----------+ | 注文ID | 商品ID | 商品名 | 単価 | 数量 | <-- 主キー: (注文ID, 商品ID) +--------+----------+----------+--------+----------+ | 1001 | P01 | リンゴ | 150 | 3 | | 1001 | P02 | バナナ | 100 | 5 | | 1002 | P03 | 牛乳 | 200 | 1 | | 1003 | P01 | リンゴ | 150 | 2 | | 1003 | P03 | 牛乳 | 200 | 2 | +--------+----------+----------+--------+----------+
このテーブルの関数従属性を分析してみましょう。
- `数量` は、どの注文(注文ID)でどの商品(商品ID)をいくつ買ったか、という情報なので、主キーである `(注文ID, 商品ID)` 全体に対して従属しています。これは問題ありません。 `(注文ID, 商品ID) -> 数量`
- しかし、`商品名` や `単価` はどうでしょうか。「リンゴ」という商品名や「150円」という単価は、注文IDには関係なく、商品ID 'P01' だけで一意に決まります。つまり、これらは主キーの一部である `商品ID` にのみ関数従属しています。 `商品ID -> 商品名`, `商品ID -> 単価`
これが「部分関数従属性」です。この状態は、以下のようなデータ異常を引き起こします。
- 更新異常: もし「リンゴ」の単価が160円に値上がりした場合、この商品ID 'P01' が登場するすべての行(注文1001と1003)の `単価` を更新しなければなりません。更新漏れが発生すれば、同じ商品に異なる単価が存在する矛盾が生じます。
- 挿入異常: まだ誰も注文していない新商品「オレンジ(P04, 300円)」を登録したいと思っても、`注文ID` が存在しないため、このテーブルにレコードを追加することができません。
- 削除異常: 注文1002がキャンセルされ、このレコードを削除したとします。もしこれが「牛乳(P03)」に関する唯一の注文だった場合、牛乳という商品の情報(商品名と単価)そのものがシステムから失われてしまいます。
第二正規形にするためには、この部分関数従属性を解消します。具体的には、主キーの一部にのみ従属している属性(`商品名`, `単価`)を、その決定子(`商品ID`)と共に別のテーブルに切り出します。
【第二正規形(2NF)に修正したテーブル】
1. 注文テーブル
注文テーブル +--------+------------+ | 注文ID | 顧客ID | ...注文ヘッダ情報 +--------+------------+ | 1001 | C001 | | 1002 | C002 | | 1003 | C001 | +--------+------------+
2. 注文明細テーブル
注文明細テーブル +--------+----------+----------+ | 注文ID | 商品ID | 数量 | <-- 主キー: (注文ID, 商品ID) +--------+----------+----------+ | 1001 | P01 | 3 | | 1001 | P02 | 5 | | 1002 | P03 | 1 | | 1003 | P01 | 2 | | 1003 | P03 | 2 | +--------+----------+----------+
3. 商品マスターテーブル
商品マスターテーブル +----------+----------+--------+ | 商品ID | 商品名 | 単価 | <-- 主キー: 商品ID +----------+----------+--------+ | P01 | リンゴ | 150 | | P02 | バナナ | 100 | | P03 | 牛乳 | 200 | +----------+----------+--------+
このようにテーブルを3つに分割することで、`商品名`と`単価`は`商品マスターテーブル`で一元管理されるようになりました。商品の価格が変更されれば、このマスターテーブルの1レコードを更新するだけで済みます。新商品の登録も、誰かが注文するのを待つ必要なく、`商品マスターテーブル`に直接追加できます。これにより、先ほど挙げたデータ異常はすべて解消されました。すべてのテーブルが第二正規形の条件を満たしています。
第三正規形(3NF: Third Normal Form) 推移的関数従属性をなくす
第三正規形は、第二正規形であることが前提です。その上で、非キー属性間の意図しない依存関係を解消します。
定義:第二正規形であり、かつ、主キー以外の属性に関数従属する非キー属性が存在しないこと。
これを「推移的関数従属性(Transitive Functional Dependency)を解消する」と言います。これは、`A -> B` かつ `B -> C` という関係があるとき、`A -> C` という関係が間接的に成立してしまう状態を指します。ここでAが主キー、BとCが非キー属性である場合、CはBを経由して間接的に主キーAに従属していることになります。これが推移的関数従属性であり、第三正規形ではこれを許しません。
先ほどの `商品マスターテーブル` をさらに拡張し、商品のカテゴリ情報を加えたケースを考えてみましょう。
【第二正規形だが第三正規形ではないテーブル例】
商品マスターテーブル +----------+----------+--------+------------+--------------+ | 商品ID | 商品名 | 単価 | カテゴリID | カテゴリ名 | <-- 主キー: 商品ID +----------+----------+--------+------------+--------------+ | P01 | リンゴ | 150 | CAT01 | 果物 | | P02 | バナナ | 100 | CAT01 | 果物 | | P03 | 牛乳 | 200 | CAT02 | 乳製品 | | P04 | チーズ | 400 | CAT02 | 乳製品 | +----------+----------+--------+------------+--------------+
このテーブルの関数従属性を分析します。
- `商品名`, `単価`, `カテゴリID` は、主キーである `商品ID` によって一意に決まります。 `商品ID -> 商品名`, `商品ID -> 単価`, `商品ID -> カテゴリID`。これは問題ありません。
- しかし、`カテゴリ名` はどうでしょうか。「果物」というカテゴリ名は、`カテゴリID` 'CAT01' によって決まります。 `カテゴリID -> カテゴリ名`。
- ここで、`商品ID -> カテゴリID` と `カテゴリID -> カテゴリ名` という連鎖が生まれています。つまり、`カテゴリ名` は非キー属性である `カテゴリID` に関数従属しています。これが推移的関数従属性です。
この設計が引き起こすデータ異常は第二正規形の場合と似ています。
- 更新異常: 「乳製品」カテゴリの名前を「デイリー製品」に変更したい場合、`カテゴリID` 'CAT02' を持つすべての商品レコード(牛乳とチーズ)を更新する必要があります。
- 挿入異常: まだ商品が一つも登録されていない新しいカテゴリ「飲料」をシステムに追加したくても、`商品ID` が存在しないため、このテーブルには登録できません。
- 削除異常: もし「果物」カテゴリの商品(リンゴとバナナ)がすべて廃番になり、レコードを削除したとすると、「果物」というカテゴリの情報そのものがシステムから消えてしまいます。
第三正規形にするためには、この推移的関数従属性を解消します。具体的には、非キー属性に従属している `カテゴリ名` を、その決定子である `カテゴリID` と共に別のテーブルとして独立させます。
【第三正規形(3NF)に修正したテーブル】
1. 商品マスターテーブル
商品マスターテーブル +----------+----------+--------+------------+ | 商品ID | 商品名 | 単価 | カテゴリID | <-- 主キー: 商品ID +----------+----------+--------+------------+ | P01 | リンゴ | 150 | CAT01 | | P02 | バナナ | 100 | CAT01 | | P03 | 牛乳 | 200 | CAT02 | | P04 | チーズ | 400 | CAT02 | +----------+----------+--------+------------+
2. カテゴリマスターテーブル
カテゴリマスターテーブル +------------+--------------+ | カテゴリID | カテゴリ名 | <-- 主キー: カテゴリID +------------+--------------+ | CAT01 | 果物 | | CAT02 | 乳製品 | +------------+--------------+
テーブルを分割したことで、カテゴリ情報は `カテゴリマスターテーブル` で一元管理されるようになりました。カテゴリ名の変更はマスターの1レコード修正で完了し、新しいカテゴリの追加も自由に行えます。`商品マスターテーブル` には外部キーとして `カテゴリID` を保持し、必要に応じて `カテゴリマスターテーブル` と結合(JOIN)することで、商品名とカテゴリ名を同時に取得できます。これで推移的関数従属性は解消され、テーブルは第三正規形を満たしました。
多くの実用的なデータベース設計において、この第三正規形までを達成することが一つの大きな目標とされています。第三正規形まで正しく適用することで、これまで見てきたデータ異常のほとんどを構造的に排除することができ、非常に堅牢でメンテナンス性の高いデータベースを構築できます。
さらなる高みへ ボイス・コッド正規形とそれ以降
第三正規形は多くの場面で十分な品質を提供しますが、理論的にはさらに厳格な正規形が存在します。ここでは、その代表的なものであるボイス・コッド正規形(BCNF)について簡単に触れておきましょう。
ボイス・コッド正規形(BCNF: Boyce-Codd Normal Form)
BCNFは第三正規形をさらに厳格にしたもので、「3.5NF」と呼ばれることもあります。その定義は非常にシンプルかつ強力です。
定義:テーブルに存在するすべての自明でない関数従属性 `X -> Y` において、決定子Xがスーパーキー(主キーまたは候補キー)であること。
ほとんどの第三正規形のテーブルは、実はBCNFの条件も満たしています。しかし、ごくまれに、複数の候補キー(主キーとして選べるキーの候補)が存在し、それらが互いにオーバーラップしている場合に、3NFではあるがBCNFではない、という状況が発生します。
例えば、「学生」「専攻」「担当教員」を管理するテーブルを考えます。以下の制約があるとしましょう。
- 1人の学生は複数の専攻を持つことができる。
- 各専攻には複数の担当教員がいるが、1人の学生に対して、その専攻の担当教員は1人に決まる。
- 各担当教員は1つの専攻しか担当しない。
この場合、`(学生, 専攻) -> 担当教員` という関数従属性と、`担当教員 -> 専攻` という関数従属性が生まれます。このテーブルの候補キーは `(学生, 専攻)` と `(学生, 担当教員)` です。このテーブルは3NFを満たしますが、`担当教員 -> 専攻` という関数従属性の決定子である `担当教員` はスーパーキーではありません(単独ではレコードを一意に特定できない)。このため、BCNFの定義には違反しています。
このようなケースでは、テーブルを `(学生, 担当教員)` と `(担当教員, 専攻)` に分割することでBCNFを達成し、より厳密なデータの整合性を保つことができます。実務でBCNF違反のケースに遭遇することは稀ですが、このようなより高度な正規形が存在することを知っておくことは、データモデリングの深い理解に繋がります。
第四正規形(4NF)と第五正規形(5NF)
さらに高度な正規形として、第四正規形(多値従属性の解消)や第五正規形(結合従属性の解消)も存在します。これらは、1つのレコード内に独立した複数の関連情報が同居してしまう、より複雑な冗長性に対処するためのものです。これらの正規形は学術的な意味合いが強く、実際のビジネスアプリケーションの設計で意識的に適用されることは非常に稀です。しかし、正規化の理論がどこまで体系化されているかを示すものとして、その存在は重要です。
現実世界との対話 パフォーマンスのための非正規化
ここまで、正規化がデータの整合性を保ち、冗長性を排除するための強力なツールであることを学んできました。理論的には、正規化のレベルを高めれば高めるほど、データベースはより美しく、クリーンな状態になります。しかし、現実のシステム開発は理論だけで完結しません。そこには常に「パフォーマンス」という非常に重要な要件が存在します。
正規化を進めると、データは複数のテーブルに分割されていきます。これはデータの重複をなくす上では正しいアプローチですが、副作用として、アプリケーションが必要な情報を一度に取得するためには、複数のテーブルを結合(JOIN)する必要が出てきます。例えば、先ほどの例で「注文ID 1001 の注文に含まれる商品の商品名とカテゴリ名の一覧」を取得するには、`注文明細テーブル`、`商品マスターテーブル`、`カテゴリマスターテーブル` の3つをJOINしなければなりません。JOIN処理は、特にデータ量が多くなると、データベースにとって負荷の高い操作となり、クエリの応答速度を低下させる主な原因となり得ます。
そこで登場するのが「非正規化(Denormalization)」という戦略です。
非正規化とは、意図的に正規化のルールを破り、パフォーマンスを向上させるためにデータの冗長性を許容する設計手法です。これは、正規化を学ばずに無秩序にテーブルを作ることとは全く異なります。非正規化は、正規化の原則とそれに伴うトレードオフを完全に理解した上で、計算されたリスクを取る、高度な設計判断なのです。
非正規化が有効なシナリオ
- データウェアハウス(DWH)やBIツール: 大量の履歴データを分析・集計するシステムでは、クエリの度に多数のテーブルをJOINするのは非現実的です。あらかじめJOIN済みの、ある程度冗長なデータ(サマリーテーブルなど)を用意しておくことで、分析クエリを高速に実行できます。これらのシステムではデータの更新頻度は低く、参照(SELECT)のパフォーマンスが最優先されます。
- 読み取り中心のアプリケーション: ブログの記事表示ページのように、書き込み(更新)よりも圧倒的に読み取り(参照)の回数が多いシステムでは、表示に必要な情報をあらかじめ一つのテーブルにまとめておくことが有効です。例えば、記事テーブルに `コメント数` や `最終コメント投稿者名` のような列を追加することが考えられます。本来これらはコメントテーブルから集計すべき情報ですが、表示の度に集計するコストを避けるために冗長に保持します。
- 検索パフォーマンスの最適化: 全文検索エンジンなどでは、検索対象のドキュメントに関連するあらゆる情報を非正規化された形で保持し、高速な検索を実現します。
非正規化を行う際は、データの冗長性によって生じる更新異常のリスクを許容しなければなりません。例えば、`記事テーブル` に `コメント数` を持たせた場合、新しいコメントが投稿されたり削除されたりするたびに、`コメントテーブル` への操作と同時に `記事テーブル` の `コメント数` も更新する処理をアプリケーション側で責任を持って実装する必要があります。この同期処理を忘れると、データの不整合が発生します。バッチ処理で定期的に同期したり、トリガーを使ったりするなど、整合性を保つための工夫が求められます。
結論として、正規化と非正規化は敵対する概念ではなく、目的と状況に応じて使い分けるべき車の両輪です。基本的な設計はまず第三正規形を目指して行い、その後、パフォーマンス要件が厳しい箇所を特定し、そこに対してのみ慎重に非正規化を適用するのが、バランスの取れたアプローチと言えるでしょう。
まとめ 設計思想としての正規化
データベースの正規化は、単なる技術的な手順ではなく、データをいかに論理的で矛盾なく構造化するかという設計思想そのものです。その旅は、データの無秩序な状態から始まり、原子性を確保する第一正規形、部分従属性を解消する第二正規形、そして推移的従属性を解消する第三正規形へと至ります。このプロセスの根底には、データ間の本質的な関係性を見抜く「関数従属性」という考え方が常に流れています。
正規化をマスターすることは、データ異常のリスクを最小限に抑え、メンテナンス性が高く、将来の変更にも強い、堅牢なシステムを構築するための基礎体力を身につけることに他なりません。そして、その原則を深く理解することで初めて、パフォーマンスという現実的な制約に対して「非正規化」という戦略的なカードを切る資格が得られます。
データベースはアプリケーションの静かな心臓部です。その設計が健全であるかどうかが、システム全体の品質と寿命を大きく左右します。正規化という羅針盤を手に、データという広大な海へ自信を持って漕ぎ出していきましょう。
以下に、本稿で最終的に到達した第三正規形のテーブル構造をSQLのDDL(Data Definition Language)で示します。これが、正規化の旅の一つの到達点です。
-- カテゴリマスターテーブル
CREATE TABLE CategoryMasters (
CategoryID VARCHAR(10) PRIMARY KEY,
CategoryName VARCHAR(50) NOT NULL
);
-- 商品マスターテーブル
CREATE TABLE ProductMasters (
ProductID VARCHAR(10) PRIMARY KEY,
ProductName VARCHAR(100) NOT NULL,
UnitPrice INT NOT NULL,
CategoryID VARCHAR(10),
FOREIGN KEY (CategoryID) REFERENCES CategoryMasters(CategoryID)
);
-- 注文テーブル
CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
OrderDate DATE NOT NULL,
CustomerID VARCHAR(10) -- 顧客マスターテーブルへの外部キー
-- 他の注文ヘッダ情報
);
-- 注文明細テーブル
CREATE TABLE OrderDetails (
OrderID INT,
ProductID VARCHAR(10),
Quantity INT NOT NULL,
PRIMARY KEY (OrderID, ProductID),
FOREIGN KEY (OrderID) REFERENCES Orders(OrderID),
FOREIGN KEY (ProductID) REFERENCES ProductMasters(ProductID)
);
この構造では、各テーブルがそれぞれ単一の責任を持ち、データの重複が最小限に抑えられています。このような設計こそが、長期にわたって価値を提供し続けるシステムの礎となるのです。
0 개의 댓글:
Post a Comment