正規化は第1段階,第2段階…と進めていくことができ、その段階によって分類されています。ここではその分類について示します。
例として示すテーブルは正規化の実例の節で挙げた販売管理に関するものと同じであり、ここではそれを定義に沿って再度見直していくものとします。定義を理解するにあたって必要な要素はすべて説明してきましたので問題はないものと思いますが、候補キー(という概念)に対する理解は特に重要ですので、主キー(という機能)との区別を含めてきちんと理解しておいてください。たまに「第2正規形とは主キーに対して…であるもの」といった説明がされることがありますが、これは主キー以外のキーが定義されていない段階での便宜的な説明です。そこを曖昧にしたまま候補キーと主キーを混同してしまうと、「主キーが設定されていなければ正規化の度合いを判断できない」あるいは「主キーが設定されていなければ正規化を進められない」といった誤解につながってしまいますので注意してください。「主キーが単一の列である場合、部分関数従属が生じないため第1正規形であれば即ち第2正規形である」というのもありがちな誤解の一つですので注意してください。
非正規形
非正規形とは正規されていない状態を指しますので、正規化した状態を定義しておけば十分ではありますが、実例を示す意味でもあえて挙げておきます。定義としては
- 単一でない値、繰り返し列のいずれかが存在する。
となります。
単一でない値とは、スペース、カンマ、改行などで区切られた複数の値を単一の値のように扱っているものを指します。
次のテーブルでは、商品コード、商品名、単価、数量においてカンマ区切りによる単一でない値が使われていますので非正規形です。
販売ID | 取引先コード | 取引先名 | 取引先所在地 | 商品コード | 商品名 | 単価 | 数量 |
---|---|---|---|---|---|---|---|
1 | 1 | 山田商事 | ○○区△△ | 1,2 | 牛乳,味噌 | 120,200 | 5000,8000 |
2 | 2 | 斉藤商店 | ◇◇町☆☆ | 2,3 | 味噌,バター | 200,190 | 16000,9000 |
3 | 1 | 山田商事 | ○○区△△ | 4 | りんごジュース | 80 | 2000 |
4 | 3 | 竹田ストア | **市□□ | 1,4 | 牛乳,りんごジュース | 120,80 | 1900,7000 |
また、次のテーブルには単一でない値はありませんが、全く同じ意味をもつ列が複数現れています。具体的には商品コード1と商品コード2、商品名1と商品名2、単価1と単価2、数量1と数量2です。このような列を繰り返し列といいます。繰り返し列があるためこのテーブルも非正規形です。
販売ID | 取引先コード | 取引先名 | 取引先所在地 | 商品コード1 | 商品名1 | 単価1 | 数量1 | 商品コード2 | 商品名2 | 単価2 | 数量2 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 山田商事 | ○○区△△ | 1 | 牛乳 | 120 | 5000 | 2 | 味噌 | 200 | 8000 |
2 | 2 | 斉藤商店 | ◇◇町☆☆ | 2 | 味噌 | 200 | 16000 | 3 | バター | 190 | 9000 |
3 | 1 | 山田商事 | ○○区△△ | 4 | りんごジュース | 80 | 2000 | ||||
4 | 3 | 竹田ストア | **市□□ | 1 | 牛乳 | 120 | 1900 | 4 | りんごジュース | 80 | 7000 |
非正規形のテーブルには多くの問題が存在します。具体的に挙げると以下のようなものです。
- 計算が複雑・困難である。
- 拡張性に欠ける。
- 行を追加できる条件(この例では販売という事実)が整わないと基本的にデータを追加できない。
- 同じ情報が繰り返し記録されるため、変更時に変更漏れがあると矛盾を生じる。
- 行を削除したときに必要以上に情報が失われることがある。
正規化を進めることにより、これらの問題を順次解決していくこととなります。
第1正規形
第1正規形の定義は次のようになります。
- 単一でない値、繰り返し列がいずれも存在しない。
非正規形のテーブルにおいて現れていた単一でない値、繰り返し列というのを解消したものが第1正規形です。どう解消するかいうと、データの量に応じて必要なだけ多くの行(列ではなく)を使うような構造のテーブルとします。
第1正規形は次のようになります。
販売ID | 取引先コード | 取引先名 | 取引先所在地 | 商品コード | 商品名 | 単価 | 数量 |
---|---|---|---|---|---|---|---|
1 | 1 | 山田商事 | ○○区△△ | 1 | 牛乳 | 120 | 5000 |
1 | 1 | 山田商事 | ○○区△△ | 2 | 味噌 | 200 | 8000 |
2 | 2 | 斉藤商店 | ◇◇町☆☆ | 2 | 味噌 | 200 | 16000 |
2 | 2 | 斉藤商店 | ◇◇町☆☆ | 3 | バター | 190 | 9000 |
3 | 1 | 山田商事 | ○○区△△ | 4 | りんごジュース | 80 | 2000 |
4 | 3 | 竹田ストア | **市□□ | 1 | 牛乳 | 120 | 1900 |
4 | 3 | 竹田ストア | **市□□ | 4 | りんごジュース | 80 | 7000 |
第1正規形となったことにより、売上額や数量の計算に関しては2つの列を参照するだけでよくなり、集計も容易な形になっていることがわかります。
第2正規形
第2正規形の定義は次のとおりです。
- 第1正規形であり、かつ、すべての非キー属性が候補キーに完全関数従属している(部分関数従属していない)。
上記の第1正規形は、1件の販売に含まれる商品の種類が1つ増えるごとに行も1つ増えるつくりになっておりますので、販売IDと商品コードを特定できれば行を1つに特定できます。つまり、{販売ID,商品コード}が候補キーとなっています。取引先コード、取引先名、取引先所在地、商品名、単価、数量はすべて非キー属性です。ところで、このテーブルでは販売IDと商品コードの両方を特定しなくとも、販売IDさえ特定できれば取引先コード、取引先名、取引先所在地を1つに特定することができます。なぜなら、1件の販売において販売先は1つしかないからです。つまり、{取引先コード,取引先名,取引先所在地}は候補キーである{販売ID,商品コード}に部分関数従属しています(完全関数従属していません)。よってこのテーブルは第1正規形ではありますが第2正規形ではありません。部分関数従属をどう解消するかというと、部分関数従属に係る列、つまり販売ID、取引先コード、取引先名、取引先所在地を取り出すようにテーブルを分割します。
販売先に関する部分関数従属を解消したテーブルは次のようになります。
販売ID | 取引先コード | 取引先名 | 取引先所在地 |
---|---|---|---|
1 | 1 | 山田商事 | ○○区△△ |
2 | 2 | 斉藤商店 | ◇◇町☆☆ |
3 | 1 | 山田商事 | ○○区△△ |
4 | 3 | 竹田ストア | **市□□ |
販売ID | 商品コード | 商品名 | 単価 | 数量 |
---|---|---|---|---|
1 | 1 | 牛乳 | 120 | 5000 |
1 | 2 | 味噌 | 200 | 8000 |
2 | 2 | 味噌 | 200 | 16000 |
2 | 3 | バター | 190 | 9000 |
3 | 4 | りんごジュース | 80 | 2000 |
4 | 1 | 牛乳 | 120 | 1900 |
4 | 4 | りんごジュース | 80 | 7000 |
販売テーブルは1件の販売ごとに1行増えるつくりになっており、候補キーは販売IDです。よって、もはや部分関数従属は存在しません。しかし、もう一方の販売明細テーブルをみると商品に関する部分関数従属が残っています。このテーブルでは依然として{販売ID,商品コード}が候補キーとなっていますが、販売IDと商品コードの両方を特定しなくとも、商品コードさえ特定できれば商品名、単価を1つに特定することができます。つまり、{商品名,単価}は候補キーである{販売ID,商品コード}に部分関数従属しています(完全関数従属していません)。よって第2正規形ではありません。そこで、この部分関数従属に係る列、つまり商品コード、商品名、単価を取り出すようにテーブルを分割し、部分関数従属を解消します。
結果として第2正規形は次のようになります。
販売ID | 取引先コード | 取引先名 | 取引先所在地 |
---|---|---|---|
1 | 1 | 山田商事 | ○○区△△ |
2 | 2 | 斉藤商店 | ◇◇町☆☆ |
3 | 1 | 山田商事 | ○○区△△ |
4 | 3 | 竹田ストア | **市□□ |
販売ID | 商品コード | 数量 |
---|---|---|
1 | 1 | 5000 |
1 | 2 | 8000 |
2 | 2 | 16000 |
2 | 3 | 9000 |
3 | 4 | 2000 |
4 | 1 | 1900 |
4 | 4 | 7000 |
商品コード | 商品名 | 単価 |
---|---|---|
1 | 牛乳 | 120 |
2 | 味噌 | 200 |
3 | バター | 190 |
4 | りんごジュース | 80 |
第3正規形
第3正規形の定義は次のとおりです。
- 第2正規形であり、かつ、どの非キー属性も候補キーに対し推移的に関数従属していない。
上記の第2正規形のうち、販売テーブルの候補キーは販売IDです。一方、取引先コードは非キー属性ですが、この取引先コードに対し取引先名、取引先所在地が関数従属しています。かつ、販売IDは取引先コードに関数従属していません。つまり、{取引先名,取引先所在地}は販売IDに対し推移的に関数従属しています。よって第3正規形ではありません。そこで、この推移的関数従属に係る列、つまり取引先コード、取引先名、取引先所在地を取り出すようにテーブルを分割し、推移的関数従属を解消します。
結果として第3正規形は次のようになります。
販売ID | 取引先コード |
---|---|
1 | 1 |
2 | 2 |
3 | 1 |
4 | 3 |
取引先コード | 取引先名 | 取引先所在地 |
---|---|---|
1 | 山田商事 | ○○区△△ |
2 | 斉藤商店 | ◇◇町☆☆ |
3 | 竹田ストア | **市□□ |
販売ID | 商品コード | 数量 |
---|---|---|
1 | 1 | 5000 |
1 | 2 | 8000 |
2 | 2 | 16000 |
2 | 3 | 9000 |
3 | 4 | 2000 |
4 | 1 | 1900 |
4 | 4 | 7000 |
商品コード | 商品名 | 単価 |
---|---|---|
1 | 牛乳 | 120 |
2 | 味噌 | 200 |
3 | バター | 190 |
4 | りんごジュース | 80 |
その他の正規形
第3正規形までの定義と実例をみてきました。理論的にはその他にボイスコッド正規形、第4正規形、第5正規形まで定義されていて、さらには第6正規形が提唱されていますが、これらについてはより詳細な概念と実例を必要としますのでここでは省略します。
しかし、実用的には第3正規形まで進めれば十分であるとされています。なぜかというと、第3正規形までの正規形では、下位の正規形に含まれている情報が完全に保たれ、正規化を進めることで何かが損なわれるということはありえませんが、ボイスコッド正規形以上の正規形では、関数従属性の一部が損なわれてしまう場合があるというのが一つの理由です。
ただし、損なわれない場合もありますので、そうした場合は正規化を進めない理由も特にありません。それでも第3正規形で十分、と言われる理由としては、
- 込み入った複雑な関数従属性が存在しない限り、第3正規形より上位の正規形を考慮する必要がそもそもないこと。
- より上位の正規化が可能であるとしても、テーブルの追加や更新、削除の状況からみて問題が発生しにくく、むしろテーブルの分割や設定、そして(クエリにおける)結合の計算コストの増大が懸念されること。
が挙げられます。前者は単なる楽観主義ともいえますが、後者の考え方は非正規化という手法につながるものです。非正規化については次の節で詳しく説明します。
テーブルの例示について
正規化を進めると上記の販売明細テーブルのようにコードや数字記号のみで構成されるテーブルも増えてきますが、このようなテーブルを単独で示しても、何を記録したテーブルなのかがわかりにくいです。そこで、このサイトにおいては、コード化されているのが自然な例であっても次の例のように名称等をそのまま表示する場合がありますのでご注意ください。
テスト名 | 科目 | 生徒番号 | 点数 |
---|---|---|---|
第1学期末 | 国語 | 1 | 75 |
第1学期末 | 国語 | 2 | 90 |
第1学期末 | 国語 | 3 | 60 |
第1学期末 | 国語 | 4 | 85 |
第1学期末 | 理科 | 1 | 80 |
第1学期末 | 理科 | 2 | 70 |
第1学期末 | 理科 | 3 | 85 |
第1学期末 | 理科 | 4 | 80 |