この特集のトップページへ
>
Chapter 3:データストア層の構築
| COLUMN テーブルの正規化 | ||
|
ここまでに,顧客情報,製品情報,伝票情報,明細情報の4つのテーブルを作ったわけだが,ここで,そのテーブル構成について補足説明をしておく。データベースについてご存じの読者であれば,「テーブルの正規化」という言葉を聞いたことがあると思う。テーブルの正規化とは,一言でいえば,「重複した値を持たないように個々のテーブルを構成する」ということである。 各テーブルに重複したデータを持たせてしまうと,データの変更が生じたときに,整合性を保って複数のレコードを変更せざるを得なくなる。それを避けるために,同じ値を含むレコードが複数あるならば,別のテーブルに分割し,そのテーブルを参照して利用する。これが,正規化の基本である(Fig.3-9)。 Fig.3-9 正規化
ところで,製品の価格という情報は,明細情報テーブルのUNITPRICEフィールドやPRICEフィールド,製品情報テーブルのPRICEフィールドという3箇所に含まれている。よって,製品情報テーブルの価格(PRICEフィールド)が変更されたら,明細情報テーブルの価格(UNITPRICEフィールドならびにPRICEフィールド)も書き換えなければならない。そういった意味で,いままで作成してきたテーブルは「正規化されていない」と考えることもできる。たしかに,明細情報テーブルに価格情報を格納せず,製品情報テーブルのPRICEフィールドを引っ張って利用するようにすれば,重複した値を排除でき,在庫情報テーブルのPRICEフィールドを更新するだけですむ可能性がある。 しかし,この方法は,実はあまり賢くない。なぜならば,製品情報テーブルの価格と明細情報テーブルの価格は一致しないことがあるためである。一般的に,製品の価格というものは固定ではない。競合製品と対抗するために価格を下げたり,為替レートの変動に伴って価格を上げたりすることは十分にあり得る。受注後に製品の価格が変わった場合,製品情報テーブルの価格(PRICEフィールド)と明細情報テーブルの価格(UNITPRICEフィールド)は一致しなくなる。もし明細情報テーブルに価格の情報を格納しておらず,製品情報テーブルから引っ張って利用するのであれば,過去に受注した製品の価格はすべて,改定後の価格に変更されてしまうだろう。 そういった理由から,製品情報テーブルにも明細情報テーブルにも価格という情報を重複して格納させているのである。 製品情報テーブルと明細情報テーブルの両者に価格という情報を重複して格納すると,もう1つのメリットが生まれる。それは,伝票に記載する価格と製品情報テーブルの価格とが一致しなくてもよくなるということである。このため,たとえば特定の顧客や一定数量のまとまった注文に対し,定価からいくらか値引いた価格を伝票に記載できるようになるのである。 また,伝票情報テーブルに存在する消費税額を保持するTAXフィールドも同様である。「消費税額は小計×0.05で求めることができるから,わざわざテーブル内に情報を保持しなくてもよいのではないか」という考えもあるだろう。しかし,計算で求めるようにしておくと,将来消費税率が変更されたときに対応できなくなる。たとえば,消費税が7%に上がったとしよう。このとき,消費税の計算式は,小計×0.07になる。一括して計算式を変更してしまうと,消費税率5%で受注していた過去の伝票の消費税も7%に変更されてしまう。そのため,伝票には消費税額(もしくは消費税率でもよいが)を保存するフィールドが必要なのである。 最後に,なぜ伝票情報テーブルに合計額のフィールド(TOTALフィールド)が必要なのかについても説明しておこう。合計金額は小計(SUBTOTALフィールド)+消費税(TAXフィールド)であるから,合計額のフィールドは不要である。そう,実際に不要なのだが,経理の計算方法によっては,必ずしもTOTALフィールド=SUBTOTALフィールド+TAXフィールドとは限らない。なぜなら,円未満の端数が生じることがあるためである。 たとえば,請求書について考えてみる。請求書は伝票に記載された額を総計したものであるが,企業によって,(1) 伝票単位で円単位に切り上げてから合算するもの,(2) 伝票に記載された額ではなく,それぞれの注文額を合算してから消費税率を掛けて月単位で集計し,最後に円単位に切り上げるもの,という2種類の方法がある。プログラム的にいえば,前者は「伝票情報テーブルのTOTALフィールドをあらかじめ円単位で切り上げておき,該当月の伝票のTOTALフィールドをすべて合算したもの」であり,後者は「詳細情報テーブルのPRICEフィールドをすべて合算したあと,消費税率を掛け,最後に円単位に切り上げたもの」である。途中,円未満の端数が出ることが予想されるので,前者の方法と後者の方法とでは,合算額が若干違うことになる(簡単にいえば,「切り上げて足すか」それとも「足してから切り上げるか」で計算結果が違うということである)。そういう理由から,その両者に対応できるように,伝票情報テーブルに合計額のフィールドを用意し,TOTALフィールドで円単位に切り上げることができるようにしたのである(ただし,本連載で作成するビジネスロジックでは,円単位の端数処理はしないことにする)。 経理処理を実装する場合には,必ず端数の処理をどうするのかという点が問題となってくる。そのため,システムの発注元とよく打ち合わせるのはもちろんとして,できるだけ柔軟な方法を採用できるようなテーブル構造にしておくのが望ましい。 以上の説明からもわかるように,テーブルの正規化は重要な作業ではあるが,必ずしも完全に正規化することが最適であるとは限らない。何らかの理由でテーブルの正規化を崩すことを,「テーブルの非正規化」と呼ぶ。テーブルの非正規化は,(1)その時点では連関性があり正規化できるが,将来的に互いに別に値が格納されることが予想され,連関性が崩れるおそれがある場合,(2)正規化したものを処理するときに多数のデータベーステーブルにまたがってアクセスする必要が生じるため,処理速度の低下が懸念される場合,によく行われる。 なお,テーブルを非正規化するか否かは,原則としてテーブルを完全に正規化したあとで検討すべきである。むやみに非正規化すれば,データ量の増大などの問題を招くことを忘れてはならない。 | ||
| Chapter 3 7/22 | ||

