非正規化とは、正規化を進めることによるデメリットを考慮し、あえて正規化を進めずに留めておくことを指します。
ここでは、非正規化が有効と考えられるケースと典型的な例、そして注意点を説明することとします。
非正規化が有効と考えられる場合
正規化を進める主な目的は、データの追加・更新・削除時の問題発生を防ぐことにあります。よって、データの追加・更新・削除を行う必要がないテーブルについては正規化を進める必要性も特にないということになります。具体的な例としては、テーブルそのものを他のシステムからインポートして専ら検索やクエリのデータソース用としてのみ用いるような場合が挙げられます。このような場合はインポートにより得られたテーブルを正規化する必要性はありません。また、データの追加・更新・削除があってもその影響を受けない部分については、あえて正規化しないことが考えられます。
また、正規化を進めると必然的にテーブルが分割されていきますが、テーブルが細分化されていく関係上、必要なクエリの作成が難しくなったりクエリの実行に時間がかかるようになる場合があります。それらが許容できない場合、データの追加、更新、削除時の問題をあえて許容し(あるいはデータマクロなどでチェックすることとして)非正規形のまま運用するということが考えられます。
非正規化の実例
代表的な例としては次のようなものが挙げられます。
繰り返し列を許容する
例としては正規化の実例の節で掲げた次のようなテーブルが挙げられます。行(レコード)の追加・削除・変更に伴う問題は山積していますし、販売する商品の数に拡張性がありません(2種までしか記録できない)が、列が足りない場合は随時追加するか複数の販売IDを割り当てて(つまり複数回販売したこととして扱う)対応しようというわけです。
販売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 |
また、ここまで極端でなくとも、繰り返し列をあえて許容するというのは、是非は別として確かに珍しくありません。商品と取引先に関する情報だけを分割し、販売した商品に関する繰り返し列を残した例は次のようになります。
販売ID | 取引先コード | 商品コード1 | 数量1 | 商品コード2 | 数量2 |
---|---|---|---|---|---|
1 | 1 | 1 | 5000 | 2 | 8000 |
2 | 2 | 2 | 16000 | 3 | 9000 |
3 | 1 | 4 | 2000 | ||
4 | 3 | 1 | 1900 | 4 | 7000 |
取引先コード | 取引先名 | 取引先所在地 |
---|---|---|
1 | 山田商事 | ○○区△△ |
2 | 斉藤商店 | ◇◇町☆☆ |
3 | 竹田ストア | **市□□ |
商品コード | 商品名 | 単価 |
---|---|---|
1 | 牛乳 | 120 |
2 | 味噌 | 200 |
3 | バター | 190 |
4 | りんごジュース | 80 |
第3正規形まで正規化すると4つになるところですが、1つ少ない3つのテーブルにまとまってはいます。
いずれ、少ないテーブルで済ませようというのは確かにシンプルな方針ではあります。ただし、上記の例ぐらいのテーブルであれば多少複雑なクエリを作っても実行時間は問題になるほど長くなりませんし、むしろ集計や抽出の際に複数の列を指定する手間が増えるので正規化のメリットを上回るということは考えにくいです。
また、理屈としては繰り返し列であっても明らかに多くの列数を要しない場合には正規化しないことが考えられます。
例としては次のようなものが挙げられます。
契約ID | 契約名 | 取引先コード | 担当者コード1 | 担当者コード2 |
---|---|---|---|---|
1 | ○○賃貸借契約 | 20124 | 102 | |
2 | △△売買契約 | 10246 | 102 | 103 |
3 | □□賃貸借契約 | 20249 | 101 | |
4 | ○○委託契約 | 20100 | 103 | 104 |
この例では、1件の契約に2人までの担当者が充てられる制度になっているものとします。担当者コード1及び2は形の上では繰り返し列であるといえますが、上記の販売テーブルの例(一度の販売に何種の商品が含まれるかは注文内容次第)と違ってその列数が自ずと限られていますし、制度改定があったとしてもせいぜい1人増えるかどうかといったところですので拡張性を考慮する必要性が薄いです。また、これらの列を使った計算も重要ではありませんので正規化(分割して担当者テーブルを作る)してもあまりメリットがないと考えられるわけです。
また、次のような例もあります。これは競技における審判スコアを記録するテーブルです。
大会コード | エントリーNo. | 選手名 | 審判A得点 | 審判B得点 | 審判C得点 | 審判D得点 | 審判E得点 |
---|---|---|---|---|---|---|---|
1 | 1 | 金子 博和 | 18.5 | 17.0 | 17.0 | 18.0 | 16.5 |
1 | 2 | 押田 信人 | 17.5 | 17.0 | 16.0 | 17.0 | 17.5 |
1 | 3 | 菅原 誠一 | 18.0 | 16.0 | 16.5 | 16.0 | 16.5 |
1 | 4 | 内田 海斗 | 15.5 | 16.0 | 15.0 | 15.5 | 16.5 |
1 | 5 | 磯崎 亮一 | 19.0 | 18.5 | 17.5 | 18.0 | 18.5 |
細かい話になりますが、上記の契約テーブルの例の担当者コードと異なり、このテーブルの得点を表す5つの列は全く均質なものというわけではありません。審判A得点というのはAという審判員がつけた得点ですし、審判B得点というのはBという審判員がつけた得点です。もしその得点を入れ替えてしまうと(順位は不動でしょうけども)競技記録としては別ものになってしまいます。そこで、このような列は繰り返し列ではないとする見方も有力です。
ただ、いずれにせよ審判の人数に関する拡張性はやはりありませんし、審判の氏名等を審判テーブルに記録しても、このテーブルとでは通常の結合(join)を行うことができないという問題もあります。しかしルール変更も滅多にあるものではないですし、審判個人を軸とした集計分析を行うでもない限り審判テーブルとの結合の必要性もありません。そして入力のしやすさからみてもテーブルを分割するメリットが薄いと判断できる、というわけです。
演算の結果を記録する
例を挙げてみます。
テスト名 | 科目 | 生徒番号 | 点数 | 科目順位 |
---|---|---|---|---|
第1学期末 | 国語 | 1 | 75 | 3 |
第1学期末 | 国語 | 2 | 90 | 1 |
第1学期末 | 国語 | 3 | 60 | 4 |
第1学期末 | 国語 | 4 | 85 | 2 |
第1学期末 | 理科 | 1 | 80 | 2 |
第1学期末 | 理科 | 2 | 70 | 4 |
第1学期末 | 理科 | 3 | 85 | 1 |
第1学期末 | 理科 | 4 | 80 | 2 |
さて、科目順位という列がありますが、ここに記録されている科目ごとの順位(正確には同一テスト同一科目での順位)は計算すれば導きだすことができる値です。テーブルに「生のデータ」として記録するというのは冗長であり、正規化の理屈でいえば第2正規形から第3正規形にする時点で取り除かれます。しかし、あるテストに関するデータの入力が終われば同じテストに関する行が追加されることはありませんし、既存の行の更新・削除も生じないため、後で順位が変動することもありません。よって順位を記録していても何の支障もないというわけです。
過去のデータを上書きせずに残す
正規化により列は整理され簡潔になりますが、それにより過去の記録を残すことができず問題が生じる場合があります。これを解決するために一見冗長と思われる列をあえて残しておくことがあります。
以下は正規化の実例の節で示した例の再掲です。
販売ID | 商品コード | 販売単価 | 数量 |
---|---|---|---|
1 | 1 | 120 | 5000 |
1 | 2 | 200 | 8000 |
2 | 2 | 200 | 16000 |
2 | 3 | 190 | 9000 |
3 | 4 | 80 | 2000 |
4 | 1 | 150 | 1900 |
4 | 4 | 80 | 7000 |
商品コード | 商品名 | 単価 |
---|---|---|
1 | 牛乳 | 150 |
2 | 味噌 | 200 |
3 | バター | 190 |
4 | りんごジュース | 80 |
この例の場合、販売明細テーブルにおける販売単価の列がなくとも、商品コードの値をもとに商品テーブルの単価を参照すれば(現在の)単価を知ることができ、数量と積算することで販売額を計算することができると考えられます。
しかし、商品テーブルに記録されている単価が変更されると、そのたびに計算結果が変わります。つまり、単価を変更する前に販売した商品の販売額も変わってしまうという問題が生じます。そこで、そうならないように販売のたびに販売単価を記録しているというわけです(販売明細テーブルの1行目と6行目の販売単価が異なっているのは、商品コード1の商品(牛乳)の価格改定が行われた形跡を示しています)。
ただし、そもそも現在の単価と過去の販売単価は質的に異なる面があります。商品テーブルの単価はいわば定価ですが、販売明細テーブルの販売単価は実販売価格であり、単価の改定だけでなくその販売ごとの要因(値引き等)により変化しうるものといえます。
そこで、
商品コード→販売単価
ではなく
{販売ID,商品コード}→販売単価
であると捉え、よって上記のテーブルも第3正規形であると見ることもできます。
非正規化に関する注意点
非正規化は本来、単に正規化しないことをいうのではありません。正規化を進めた結果がどのようなもので、そのデメリットがどの程度であるのかをきちんと把握し比較検討した上であえて正規化の段階を退行させることをいいます。
それを行わないまま、あるいは正規化そのものを理解しないまま、パフォーマンスを優先しましたとか正規化のコストとバランスをとりました、などというのは単なる手抜きであり、本来やってはならないことです。
そもそも、0コンマ何秒を争うようなパフォーマンスを要求するシビアなシステムがあるとして、クエリの動作が遅いのであればSQLを見直すことを考慮すべきですし、ハード面から改善することも視野に入れるべきです。また、そのようなシビアなシステムであるからこそ、データの不整合という本来致命的な問題を安易に許容すべきではありません。非正規化の実施は慎重に判断しなければなりません。