人類みんなごくつぶし

2004-04-16

[]正規化 17:15

自分の理解のために正規化についてちょっとまとめてみます。


  • 正規化とはリレーショナルデータベースのキレイな設計方針の一つです。
  • 正規化の目的は、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正規化の定義
  • 第1正規化であること
  • すべての非キー属性は、候補キーに対して、部分関数従属していないこと(完全関数従属であること)
関数従属というのは、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正規化の定義
  • 第2正規化であること
  • すべての非キー属性は、候補キーに対して、推移的関数従属していないこと
推移的関数従属というのは、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の正規形もありますが、 疲れたのでやめときます。
あー、なんか問題とくのめんどくさい現実逃避な気がしてきた。。。

[]正規化を行わない理由 17:32

正規化は、よい設計指針なんですが、あえて正規化をおこなわない、あるいは、非正規化の方向にテーブルを変更することがあります。正規化を行わない理由には以下のものがあります。

  • 正しい履歴を残したい場合

上の例では、単価は、商品購入時の単価である必要があります。後で価格改正した場合、価格が昔の購入データにまで反映されるとおかしなことになります(価格は商品コードを買える場合も多いだろうで、消費税などを例に考えた方が現実的かも)。


マスタテーブルと同じカラムや導出項目を定義しておくことで、ジョインや計算の処理を省き高速化させます


第3正規化までは、情報無損失分解といって、テーブルのジョインで、必ず分解前のビューを復元することができます。これ以降のボイスコット正規化などでは、元の情報が失われることがあるので、そのため、第3正規化まででとどめたりもするらしい。



その他にもいくつかあるかもしれません。。。

[]キーの種類 17:44

主キー、外部キー、ユニークキー、キーにもいろいろありますね。



スーパーキー(super key)

  • 行を一意に識別するキー
  • 一意に識別すればいいので、カラム全部というのもあり

候補キー(candidate key)

  • スーパーキーの中で極小のもの

主キー(primary key)

  • 候補キーが複数存在する場合、そのうちの一つが主キー
  • 候補キーとの明確な違いは、NOT NULLであること
  • NOT NULLの候補キーが複数ある場合、どれが主キーであるかは、イマイチ理解不能だったりする。


外部キー(foreign key)

  • 制約の一つ
  • 外部テーブルのカラムが存在することを保証する
  • DBによって、参照先のカラムが消されたら、参照元のカラムも消える、NULLを入れる、エラーにする、などのオプションを設定できたりする。

ユニークキー(unique key)

  • 制約の一つ
  • このテーブルで一意であることを保障する。
  • どういう場合に使うのかよくわからない。NOT NULLでいいかも知らない。

(制約の1つというと全部そうなるか?)

もっとあるかも。。。

[] SunのRIをシステムクラスローダー以外から読み込む 03:21

(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 で似たような処理をやってたような気がする。

habuakihirohabuakihiro 2004/04/17 10:33 すんません、One Fact ”In” One Place ということでm(._.)m

muimymuimy 2004/04/17 22:09 ぎゃー、つっこみありがとうございます。修正しました。

igapyonigapyon 2004/04/18 00:19 私も昔、データベース試験に 何度か挑戦して、、、しかし私の場合、完全にあきらめました (T_T)

muimymuimy 2004/04/18 03:33 いろんな意味で難解ですよね。。。。明日がんばります。