2004-04-16
■[データベース試験]正規化
自分の理解のために正規化についてちょっとまとめてみます。
- 正規化とはリレーショナルデータベースのキレイな設計方針の一つです。
- 正規化の目的は、1 fact in 1 place (一事実一箇所) にすることで、データの更新時の不整合、データの冗長性を排除することです。
- 正規化には第1正規化〜第5正規化まであり、その他にボイスコット正規化などもあります。
- この中で一般的に使われるのは、第3正規化までです。
なお、以下の説明(特に定義の部分)は、情報処理教科書データベース(ISBN:4798104957)を参考にしました(この参考書、説明が小難しくなくていいかも)。
非正規化
非正規化は第1正規化にもなってない状態です。
例えば次の、コンビニのお買い物伝票テーブルの例(大分簡略化してます)は、非正規化のテーブルの例です。
伝票
| 伝票番号
| 商品コード | 商品名 | 単価 | 数量 | 小計 | 合計 | 販売員コード | 販売員名 |
| 0001 | AAAA | 飲むヨーグルト | 200 | 1 | 200 | 600 | AAAA | 山田太郎 |
| BBBB | 1Mカテキン茶 | 200 | 2 | 400 | ||||
| 0002 | CCCC | バナナ弁当 | 500 | 1 | 500 | 700 | BBBB | ジョン万次郎 |
| BBBB | 1Mカテキン茶 | 200 | 1 | 200 |
このデータをRDBのテーブルで表現しようと思ったら、1つのレコードに 商品コード1、商品名1、商品コード2、商品名2、、、 といった「繰り返し項目」が存在することになります。 「繰り返し項目の存在」は、
- 拡張性に乏しい(1度に買える商品の最大数が決まっている)
- 検索処理を複雑(商品ごとの統計をとったりするSQLが複雑よ)
第1正規化
第1正規化の定義
つまり、繰り返し項目をなくした状態です。 先のテーブルから繰り返し項目部分を「明細」として別テーブルに分割します。
伝票
| 伝票番号 | 合計 | 販売員コード | 販売員名 |
|---|---|---|---|
| 0001 | 600 | AAAA | 山田太郎 |
| 0002 | 700 | BBBB | ジョン万次郎 |
明細
| 伝票番号 | 商品コード | 商品名 | 単価 | 数量 | 小計 |
|---|---|---|---|---|---|
| 0001 | AAAA | 飲むヨーグルト | 200 | 1 | 200 |
| 0001 | BBBB | 1Mカテキン茶 | 200 | 2 | 400 |
| 0002 | CCCC | バナナ弁当 | 500 | 1 | 500 |
| 0002 | BBBB | 1Mカテキン茶 | 200 | 1 | 200 |
第2正規化
第2正規化の定義
関数従属というのは、A→B(Aが決まればBも決まる、BがAに従属している)という関係です。 例えば、商品コード→商品名、が関数従属です。
上記明細表では、(伝票番号、商品コード)という組が主キーになりますが、 この2つに対して従属してるのはいいですが、 このうちの一部(どちらか)に従属しているというのは少し問題です。 この一部に従属しているという意味で、部分関数従属と呼ばれます。
第2正規化にするには、上の明細テーブルから商品テーブルを分離します。
商品
| 商品コード | 商品名 | 単価 |
|---|---|---|
| AAAA | 飲むヨーグルト | 200 |
| BBBB | 1Mカテキン茶 | 150 |
| CCCC | バナナ弁当 | 500 |
明細
| 伝票番号 | 商品コード | 数量 | 小計 |
|---|---|---|---|
| 0001 | AAAA | 1 | 200 |
| 0001 | BBBB | 2 | 300 |
| 0002 | CCCC | 1 | 500 |
| 0002 | BBBB | 1 | 200 |
もし1Mカテキン茶の名前を変えたいときは、商品テーブルの該当レコードのみ更新すればよいことがわかります。
第3正規形
第3正規化の定義
推移的関数従属というのは、A→B→C (Aが決まればBもきまる、Bが決まればCも決まる)という関係です。 例えば、上の伝票テーブルの、伝票番号→販売員コード→販売員名 というのは、推移的関数従属の関係です。A→B と B→C を分けることで、より、One Fact One Place に近づきます。 第3正規形にすると、伝票テーブルから、販売員テーブルが分離されます。
販売員
| 販売員コード | 販売員名 |
|---|---|
| AAAA | 山田太郎 |
| BBBB | ジョン万次郎 |
伝票
| 伝票番号 | 合計 | 販売員コード |
|---|---|---|
| 0001 | 600 | AAAA |
| 0002 | 700 | BBBB |
また、第3正規形では、商品テーブルの小計や、伝票の合計などの、 他の値から導きだせる導出項目も取り除きます。 これらは、別テーブルにするのでなく、カラムから削除します。
ここまでで第3正規形です。 ボイスコット正規形、第4、5の正規形もありますが、 疲れたのでやめときます。
あー、なんか問題とくのめんどくさい現実逃避な気がしてきた。。。
■[データベース試験]正規化を行わない理由
正規化は、よい設計指針なんですが、あえて正規化をおこなわない、あるいは、非正規化の方向にテーブルを変更することがあります。正規化を行わない理由には以下のものがあります。
- 正しい履歴を残したい場合
上の例では、単価は、商品購入時の単価である必要があります。後で価格改正した場合、価格が昔の購入データにまで反映されるとおかしなことになります(価格は商品コードを買える場合も多いだろうで、消費税などを例に考えた方が現実的かも)。
マスタテーブルと同じカラムや導出項目を定義しておくことで、ジョインや計算の処理を省き高速化させます
- 元の情報が失われる
第3正規化までは、情報無損失分解といって、テーブルのジョインで、必ず分解前のビューを復元することができます。これ以降のボイスコット正規化などでは、元の情報が失われることがあるので、そのため、第3正規化まででとどめたりもするらしい。
その他にもいくつかあるかもしれません。。。
■[データベース試験]キーの種類
主キー、外部キー、ユニークキー、キーにもいろいろありますね。
スーパーキー(super key)
- 行を一意に識別するキー
- 一意に識別すればいいので、カラム全部というのもあり
候補キー(candidate key)
- スーパーキーの中で極小のもの
主キー(primary key)
- 候補キーが複数存在する場合、そのうちの一つが主キー
- 候補キーとの明確な違いは、NOT NULLであること
- NOT NULLの候補キーが複数ある場合、どれが主キーであるかは、イマイチ理解不能だったりする。
外部キー(foreign key)
- 制約の一つ
- 外部テーブルのカラムが存在することを保証する
- DBによって、参照先のカラムが消されたら、参照元のカラムも消える、NULLを入れる、エラーにする、などのオプションを設定できたりする。
- 制約の一つ
- このテーブルで一意であることを保障する。
- どういう場合に使うのかよくわからない。NOT NULLでいいかも知らない。
(制約の1つというと全部そうなるか?)
もっとあるかも。。。
■[RowSet] SunのRIをシステムクラスローダー以外から読み込む
(http://d.hatena.ne.jp/muimy/20040320#p7 のつづき)
SunのRowSet実装(RI)はシステムクラスパスに通して使う分はいいですが、
サーブレットコンテナやEclipseプラグインなどから使うと、
CachedRowSetImplなどのnew時にNullPointerExceptionになります。
(あ、サーブレットコンテナは試してないけど、たぶん)
ヌルポの元のSyncFactory#initMapIfNecessary を
見ると次のような処理を行っているようです。
String s = System.getProperty("rowset.properties");
if(s != null)
{
ROWSET_PROPERTIES = s;
properties.load(new FileInputStream(ROWSET_PROPERTIES));
parseProperties(properties);
}
ROWSET_PROPERTIES = "javax" + strFileSep + "sql" + strFileSep + "rowset" + strFileSep + "rowset.properties";
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
properties.load(classloader.getResourceAsStream(ROWSET_PROPERTIES));
parseProperties(properties);
カレントスレッドのクラスローダからrowset.properties読み込んでます。
なので、rowset.jarをクラスパスに持つクラスローダーが、
rowset.propertiesが見つからないことになります。
(普通にSyncFactory.class.getResourceAsStream(...)
してくれればいいかと思うが、、、Tigerに組み込む関係とかあるのかな?)
対処として、次のような感じで使うとエラーなく読み込めるようです。
public static CachedRowSet getCachedRowSet() throws SQLException{
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
ClassLoader newLoader = MyHogeClass.class.getClassLoader();
Thread.currentThread().setContextClassLoader(newLoader);
CachedRowSetImpl rowset = new CachedRowSetImpl();
Thread.currentThread().setContextClassLoader(oldLoader);
return rowset;
}
//CachedRowSet rowset = new CachedRowSetImpl(); CachedRowSet rowset = getCachedRowSet();
やー、これが適切な手段か分からんけどー。
JNDI Explorer for Eclipse で似たような処理をやってたような気がする。
■[RowSet]WebLogic Server における RowSet の使い方
http://edocs.beasys.co.jp/e-docs/wls/docs81/jdbc/rowsets.html
habuakihiro
2004/04/17 10:33
すんません、One Fact ”In” One Place ということでm(._.)m
muimy
2004/04/17 22:09
ぎゃー、つっこみありがとうございます。修正しました。
igapyon
2004/04/18 00:19
私も昔、データベース試験に 何度か挑戦して、、、しかし私の場合、完全にあきらめました (T_T)
muimy
2004/04/18 03:33
いろんな意味で難解ですよね。。。。明日がんばります。
- http://d.hatena.ne.jp/igapyon/20040416
- http://d.hatena.ne.jp/atsushifx/20040416
- http://d.hatena.ne.jp/uno/20040416
- http://d.hatena.ne.jp/manhole/20040416
- http://d.hatena.ne.jp/muimy/20040416
- http://d.hatena.ne.jp/muimy/20040513
- http://d.hatena.ne.jp/satoshis/20040820
- http://d.hatena.ne.jp/YourHouse/20040822