データの正規化とE-R図

技術の仕組み | データベースの基礎

← 技術の仕組み

正規化は「表をきれいに分ける作法」ではなく、同じ事実を1か所にだけ置くための操作です。1つの表に何もかも詰め込むと、同じ事実が何行にも散り、片方だけ直して矛盾する(更新時異常)。だから分ける。そして分けた表どうしを外部キーでつなぎ直した設計図がE-R図です。試験で問われるのは「どこまで分けたか(正規形)」と「どちらが多か(多重度)」の2点に集約されます。

先に押さえる

関係データベースの表は行の集まりで、行を1本に特定する列(の組)が主キーです。正規化は「主キーが決まれば、その行の他の列が自動的に決まる」状態(=関数従属が主キーからだけ伸びる状態)に近づける作業。逆に言えば、主キー以外の列が別の列を決めてしまう形が残っていると、そこが冗長=異常の発生源になります。

なぜ正規化するか ― 更新時異常

受注を1枚の表にまとめた例です。同じ商品の名前と単価が何行にも複製されています。

受注表(正規化していない・1行に明細を並べた形)
受注番号受注日顧客番号顧客名顧客住所商品番号1商品名1単価1数量1商品番号2商品名2単価2数量2
100104-01C01山田商事東町1-1P10A型ケース1,2003P20B型カバー8001
100204-02C02青木物産西町2-2P10A型ケース1,2005(空)(空)(空)(空)
100304-03C01山田商事東町1-1P10A型ケース1,2502P30C型台座5001
主キー同じ事実の重複
見抜きどころ

3行目だけ単価が1,250になっています。どちらが正しいのか、この表からは決められません。これが更新時異常です。1つの事実(P10の単価)が複数の場所に散っていると、更新はその全部に届かなければならず、1か所でも漏れれば矛盾が残る。ほかにも挿入時異常(受注がまだ無い新商品を登録できない)、削除時異常(最後の受注を消すと商品の情報まで消える)が同じ原因から起きます。「同じ値が何度も現れる列」を本文の表から見つけたら、そこが分割対象です。

第1〜第3正規形 ― 同じ表がどう分かれるか

  1. 第1正規形(1NF)=繰返し項目をなくす。「商品1・商品2…」と横に並べた列を、行方向に展開する。1つのマスには1つの値だけ。
  2. 第2正規形(2NF)=部分関数従属をなくす。主キーが複数列の組のとき、その一部だけで決まる列を別表に出す。
  3. 第3正規形(3NF)=推移的関数従属をなくす。主キー以外の列がさらに別の列を決めている関係を別表に出す。
第1正規形へ ― 繰返しを行にほどく
受注明細表(1NF・主キーは 受注番号+商品番号 の組)
受注番号商品番号受注日顧客番号顧客名顧客住所商品名単価数量
1001P1004-01C01山田商事東町1-1A型ケース1,2003
1001P2004-01C01山田商事東町1-1B型カバー8001
1002P1004-02C02青木物産西町2-2A型ケース1,2005

繰返しは消えましたが、まだ重複が残っています。受注日と顧客は受注番号だけで決まり、商品名と単価は商品番号だけで決まる。つまり主キーの一部だけで決まる列(部分関数従属)が混ざっている状態です。

第2正規形へ ― 主キーの一部で決まる列を出す
受注表(2NF)
受注番号受注日顧客番号顧客名顧客住所
100104-01C01山田商事東町1-1
100204-02C02青木物産西町2-2
100304-03C01山田商事東町1-1
明細表(2NF)
受注番号商品番号数量
1001P103
1001P201
1002P105
商品表(2NF・単価はここに1か所だけ)
商品番号商品名単価
P10A型ケース1,200
P20B型カバー800

単価の重複は消えました。ただし受注表には、受注番号→顧客番号→顧客名・顧客住所という玉突きの従属(推移的関数従属)が残っています。顧客が引っ越したら受注行を全部直すことになる。

第3正規形へ ― 玉突きの従属を切る
受注表(3NF・顧客は番号で参照するだけ)
受注番号受注日顧客番号
100104-01C01
100204-02C02
100304-03C01
顧客表(3NF・顧客の事実はここに1か所だけ)
顧客番号顧客名顧客住所
C01山田商事東町1-1
C02青木物産西町2-2
主キー外部キー
つまずきやすい

「2NFは部分従属、3NFは推移従属」を丸暗記すると、設問の表で判定できません。手順にすると簡単です。①主キーを確定する→②各列について『この列は主キー全体がないと決まらないか』を1つずつ問う。主キーの一部だけで決まるなら2NF違反、主キーでない列から決まるなら3NF違反。判定の入口は常に主キーの確定で、ここを間違えると全部ずれます。なお、単一列が主キーの表には「主キーの一部」が存在しないので、1NFなら自動的に2NFです。

主キー・候補キー・外部キー・参照整合性

行を一意に決める側

  • 候補キー:行を一意に決められる列(の組)で、余分な列を含まないもの。複数あってよい(例:商品番号と、商品の型番)。
  • 主キー:候補キーの中から1つ選んだもの。空値(NULL)にできず、重複もできない。
  • 複合キー:2列以上の組で一意になる主キー(明細表の 受注番号+商品番号)。

他の表を指す側

  • 外部キー他の表の主キーを指す列。値は参照先に実在するか、空値のどちらかでなければならない。
  • 参照整合性:「指した先が必ずある」状態を保つ制約。存在しない顧客番号の受注は登録できない。
  • 削除時の扱い:参照されている顧客を消そうとしたら、拒否する/連動して受注も消す/外部キーを空にするのいずれか。設問では「どれを選ぶと業務上まずいか」が問われる。
顧客顧客番号(主キー)
顧客名/顧客住所
1 ──────→ 多1対多受注が顧客番号を持つ
受注受注番号(主キー)
受注日/顧客番号(外部キー)
1対多(明細が受注番号を持つ)
明細受注番号+商品番号(主キー)
数量
1対多(明細が商品番号を持つ)
商品商品番号(主キー)
商品名/単価

E-R図の読み方 ― 多重度の向きを外部キーで決める

エンティティ=表になるもの(顧客・受注・商品)、関連=エンティティ間の線、多重度=その線の両端が「1」か「多」かの印です。

見抜きどころ

関連の向きを迷ったら、外部キーを持っている側が「多」と覚えます。受注表が顧客番号を持つ=顧客1に対し受注が多。1件の顧客番号は受注表の何行にも現れられるが、1本の受注行が持てる顧客番号は1つだけだからです。設問で「未記入の多重度を答えよ」と問われたら、その表の列に相手の主キーが入っているかどうかを見に行けば決まります。文章から読むときは両方向で言い切ってみるのも有効です(「1人の顧客は複数の受注をする/1件の受注は1人の顧客のもの」=1対多)。

注記

多対多はそのままでは表にできません。受注と商品は多対多ですが、間に「明細」という表(連関エンティティ)を置き、両側の主キーを外部キーとして持たせて複合主キーにすることで1対多×2に分解します。E-R図に多対多が残っていたら、そこに表が1枚増えるサインです。設問の「追加すべきエンティティを答えよ」はほぼこの形。

急所 ― 非正規化と集計表

非正規化はトレードオフ

  • 正規化を進めるほど表が増え、参照は結合の回数が増えて遅くなる
  • そこであえて冗長に持つ(受注時点の単価を明細に写す、合計金額を持つ)のが非正規化。
  • ただし更新時異常のリスクが戻ってくる。「速さを買って整合性を自分で守る義務を負う」取引だと理解する。
  • 受注時点の単価を明細に持つのは冗長ではない場合がある。商品表の単価は「現在の単価」、明細の単価は「その時いくらで売ったか」で別の事実だから。設問で頻出の引っかけ。

集計表の主キーは集計軸

  • 月次の売上集計のような表では、何を単位に集めたか(集計軸)がそのまま主キーになる。
  • 「商品別・月別の売上」なら主キーは商品番号+年月。1つの軸を落とすと行が重複して一意でなくなる。
  • 逆に、問題文の集計表の見出しから軸を数えれば主キーの列数が決まる。空欄補充はここで解ける。
  • 集計値(合計・件数)は元表から導けるので、主キーには絶対に入らない
なぜ問われるか

データベースの設問は、業務の説明文を読んで表とE-R図に写せるかを測っています。だから問われ方は毎年ほぼ同じ形をとります――(1)未完成のE-R図の多重度や関連線を埋める、(2)表の主キー・外部キーに下線を引く、(3)業務の追加要件(履歴を残す・複数担当を許す)に対しどの表を足すかを答える。(3)はいずれも「1つのマスに複数の値を入れたくなったら表を分ける」という1NFの原則の応用です。要件が『複数の…』と言った瞬間に、表が1枚増えると身構えられれば、その場で解けます。

この仕組みを使う設問