PREPARATION
受験対策

OSS-DB入門

第4回 データベースの正規化

今回はデータベースの正規化について取り上げます。
PostgreSQL固有ではなく、RDBMS全般に関わる一般的なテーマですが、データベースを扱う上で重要な下地となる部分ですので、ぜひ基礎固めとしてご利用ください。「正規化」はOSS-DB Exam Silverの出題範囲にも含まれていますので、受験を考えている方も気を抜かずに取り組みましょう。

正規化とは

データの重複をなくし整合的にデータを取り扱えるようにデータベースを設計することを、データベースの正規化と呼びます。正規化を行っておくと、データの追加・更新・削除などに伴うデータの不整合や喪失が起きるのを防ぎ、メンテナンスの効率を高めることができます。
正規化の段階には、第1~第5正規形およびボイスコッド正規形がありますが、ここでは、データベースを設計する際に一般的に用いられる第1~第3正規形までを、前回登場したテーブルをもとにして説明していきます。

非正規形

正規化がまったく行われておらず、1行の中に複数の繰り返し項目が存在するようなテーブルは非正規形と呼びます。
前回は、ある卸売業者の商品管理表だけをみてきましたが、この商品管理表は入荷業務を進める際には仕入先に紐づけて処理されています(仕入先ごとにバインダーで綴じて管理しているようなイメージです)。そこで、仕入先も考慮に入れた表を描いてみましょう。なお、入荷処理を見ていくわけですから、在庫数だけではなく入荷数のカラムも追加しておきます。

※本連載で例示するテーブルは、卸売業務を想定したデータ群のうち、記事のテーマに添った箇所を部分的に抜粋し簡略化したものです。例えば出荷処理のことを考慮から外していたり、仕入先や代表者のIDを設定していない(名称のみで一意に識別可能とし、同名はないものと仮定している)など、業務データとしては不十分な点もあることをご了承ください。
商品管理表
この表の場合、ひとつの仕入先に対して、複数の商品が繰り返して存在しているため、非正規形に該当します。

第1正規形

リレーショナルデータベースでは原則としてレコード単位で個々のデータを扱いますので、非正規形のデータはデータベースに格納することすらできません。まずは、繰り返し項目のそれぞれを別レコードとして独立させます。
また、在庫数は入荷数の合計から導出することができます。このように他のカラムから導出可能な項目は、この段階で削除しておきます。

※普通、在庫数は (入荷数の合計 - 出荷数の合計) から求めるものですが、上述のように、ここでは簡易的に入荷業務のみを考えています。

この結果、表は以下のようになります。
商品管理表
繰り返し項目を持たず、導出項目の排除されたこの表は第1正規形に該当します。

第2正規形

第1正規形にすることでデータベースに格納できるようにはなりましたが、データ管理の観点からはまだまだ不十分です。例えば、新たな仕入先と契約したとしても、実際に何らかの商品を入荷するまでは、その情報を管理することができません。また、仕入れ先の代表者名が変更になると、複数のレコードを更新しなければならないため不整合を生じる恐れがあります。これは、仕入先情報、商品情報、入荷履歴といった独立した情報をすべて同一のレコードで扱っていることが理由です。これらを別々のテーブルに分割することを考えましょう。

第1正規形のテーブルを見てみると、レコードを一意に定める要素は{仕入先, 商品名, 入荷日}(これを主キーと呼びます)であることが分かります。主キー以外の項目(非キー属性)について、主キーの一部の要素だけで決まる(部分関数従属と言います)ものがあれば別テーブルに分離させましょう。各項目の従属関係は次の図を参照してください。
図1. 各項目の従属関係  ※下線は主キーであることを示します

テーブルを分割させた結果は次のようになります。
商品テーブル
入荷テーブル
いずれのテーブルにおいても非キー属性は主キーに従属する形になりました。この状態を完全関数従属と呼び、こちらを満たしている形式が第2正規形となります。

第3正規形

テーブルを分割することで、データの冗長性もだいぶ取り除かれました。しかし、まだ十分とは言えません。津軽ファームの代表者である陸奥太郎さんが他の農園の代表も兼任しているとしたらどうなるでしょう。陸奥太郎さんの連絡先が変更された場合には、やはり複数レコードを更新する必要が出てきてしまいます。
主キー以外の項目について項目同士で依存関係を持っているもの(推移的関数従属と言います)も、別テーブルに切り分けましょう。
商品テーブル
仕入先テーブル
電話帳
入荷テーブル
このように、非キー属性がすべて主キーに完全関数従属し、なおかつ推移的関数従属性の排除された形式を第3正規形と呼びます。

まとめ

正規化の作業を通じて、データの冗長性が軽減され、整合的に管理できるようになっていく過程がつかめたでしょうか。次回は実際にSQLを用いてデータの挿入・更新・削除といったことを行いますので、正規化のメリットをより強く実感できるものと思います。
それぞれの正規形の条件をあらためて以下にまとめます。
第1正規形
・繰り返し項目を持たない
・導出項目を持たない
第2正規形
・第1正規形を満たしている
・主キーに対してすべての非キー属性が完全関数従属
第3正規形
・第2正規形を満たしている
・すべての非キー属性がどの候補キーに対しても推移的に関数従属していない
本章では、非正規形から第3正規形まで段階的に正規化を進めてきましたが、実際の業務では一足飛びにいきなり第3正規形で設計をすることもあると思います。また逆に、検索効率を考えて、あえて正規化の程度を落とすこともあるでしょう。手順に捉われ過ぎることなく、アプリケーションの利用シーンやパフォーマンス要件などに応じて柔軟にデータベースを設計できるようになることを目指しましょう。
データの正規化については、データベースに関する書籍やネット上の記事などで数多く取り扱われています。ぜひご自身でも調べてみて、理解を深めていってください。

補足

今回正規化したテーブルをデータベースに登録するためのSQLは以下のようになります。
前回作成したproductsテーブルは、正規化の過程で若干変更されましたので、DROP TABLEでテーブルを一旦削除して、再度CREATE TABLEを実行しています。(テーブルに既にデータが入力されている場合や、他のデータベースオブジェクトから参照されているようなケースでは、ALTER TABLEを使って既存テーブルにそのまま変更を加えることもできます。)
また、それぞれのテーブルの主キーに対しては、PRIMARY KEYを指定することで主キー制約を付与しています。これにより、当該カラムの値から一意にレコードを定められることが保証されます。NOT NULLとUNIQUE制約を組み合わせることでも同等の効果は得られます。

DROP TABLE products;
CREATE TABLE products(
name varchar(255) PRIMARY KEY,
price integer CHECK (price > 0),
etc text
);
CREATE TABLE supplier(
farm_name varchar(255) PRIMARY KEY,
address varchar(255),
rep_name varchar(255)
);
CREATE TABLE telbook(
rep_name varchar(255) PRIMARY KEY,
tel varchar(20)
);
CREATE TABLE orders(
name varchar(255),
farm_name varchar(255),
arrival_date date,
order_num integer,
PRIMARY KEY(name, farm_name, arrival_date)
);

執筆:
平敷 兼貴 氏(OSS-DB Gold認定者)株式会社オークニー ジオソリューション事業部
FOSS4G(オープンソース地理空間ソフトウェア)を活用したアプリケーションやサービスの設計・開発に従事。
当連載の執筆を契機にPostgreSQLを再勉強し、2013年初めにOSS-DB Goldを取得。PostgreSQLマスターを目指して日々研鑽中。

アドバイザー:
森 亮 氏 株式会社オークニー代表取締役
2002年にオークニーを設立し、代表取締役に就任。マッピング・ GIS・LBS・ITS関連プロジェクトのビジネスコンサルティングを経て、現在は会社経営の傍ら、『入門Webマッピング』(Tyler Mitchell原著;オライリー・ジャパン発行)の翻訳、Open Source Geospatial財団日本支部の代表者を務めるなど、FOSS4G(オープンソース地理空間ソフトウェア)の普及活動に取り組んでいる。

監修:
松田 神一

LPI-Japan
Platinum Sponsors

関連資格