関数従属性

 正規化の実際の様子について「正規化の実例」の節でみてきましたが、ここからは正規化の各段階を理論的に整理し理解するために必要な、関数従属性という概念と基本的な類型について説明します。
 併せて説明する候補キーとともに、完全関数従属、部分関数従属、推移的関数従属という関数従属の類型が理解できれば、(一般に十分とされる)第3正規形までの正規化を理解するための準備が整うこととなります。

関数従属性とは

 関数従属性という言葉は、Accessというソフトをいくら使っていても現れるものではありません。それはAccessの機能ではなく、あくまで理論的な存在です。しかし、同じく理論的に重要である候補キーを理解し、正規化の定義を理解するために欠かせないものであり、テーブルの構成を決めデータベースを構築するための具体的な判断材料となるものです。
 「関数従属性」などと名前が物々しく難しそうな印象がありますが、要するに「列Aの値を1つに特定したとき、列Bの値も1つに特定できる」という関係を整理したものに過ぎません。確かに考えるほど奥が深い面もありますが、基本的には難しいものではありません。

関数従属性の例と表記

 例として次のような社員テーブルがあるとします。社員番号は各社員に1つ割り当てられ、重複しないものとします。

社員テーブル
社員番号氏名住所
1竹田 博和○○市☆☆2−2
2井上 美咲□□市**6−12
3竹田 博和××町□□6−9
4国府 伸一○○市△△3−1
5松嶋 優斗◇◇区◎◎4−6

 このとき、社員番号が1つに特定されれば、それに対応した氏名も1つに特定されます(同姓同名の社員がいますが、社員番号から氏名が1つに特定されることに違いはありません)。この関係を「氏名は社員番号に関数従属している」といいます。また、これを記号化し

社員番号→氏名


と表します。この例では、同様に社員番号が特定されれば住所も特定されます。よって

社員番号→住所

でもあります。また、これらの関係をまとめて

社員番号→{氏名,住所}

と表すことができます。
 別の例として、次のようなクラス名簿があるとします。

クラス名簿
クラス番号出席番号氏名
11藤井 美優
12平本 和之
13大西 遥
14長澤 麻友
21秋元 大樹
22益岡 泰三
23照井 桃子

クラス番号だけでは氏名を特定できませんし、出席番号だけでも氏名を特定できません。しかし、クラス番号と出席番号を両方特定できれば氏名を特定できます。
 この関係を、「氏名は{クラス番号,出席番号}に関数従属している」といい、

{クラス番号,出席番号}→氏名

と表します。
 ただし、この場合、クラス番号だけでは氏名を特定できませんので、

クラス番号→氏名

は成り立っておらず、同様に

出席番号→氏名

も成り立たないことに注意してください。

値が特定できるということ

 上記では実際に存在するテーブルの内容をもとに関数従属性の例を挙げましたが、関数従属性は本来、テーブルを作成してデータを入力して初めて判断が可能になるというものではありません。現実的には既存のテーブルがあれば有力な判断材料になり得ますが、テーブルに含めようとするさまざまな列の意味や定義について調べたうえで判断しなければなりません。
 上記の例に戻りますが、社員番号の例では同姓同名の社員がいるため

氏名→社員番号

が成り立たないことは明らかです。では、氏名に重複がなければ成り立つ、つまり氏名から社員番号が特定できるものと考えてよいのでしょうか。氏名に重複がないとしてもそれはたまたまで、一般に同姓同名の社員がいることは許容されますので答えはノーです。ただし、「この会社では同姓同名の者は採用しない。婚姻等によりそうなった場合はいずれかの者が退職となり記録もすべて削除される」といったルールがあるとすれば、成り立つといって差し支えないこととなります(現実的には考えられませんが)。要は実際の値のみをもって判断するのではなく、それぞれの列についてどのような性質を持っているのかをきちんと整理したうえで判断すべきということです。ちなみに、テーブルの具体例に基づいて関数従属を判断させるという(試験)問題を見かける場合があります。これは、テーブルに含まれているレコードの内容が一通りの変化を網羅している、という暗黙の了解があって成り立つものであり、テーブルに存在する値だけを見て関数従属を判断するという考え方自体は決して適切とはいえません。
 ただ一方で、どういう理屈であれ(「ある列の値を1つに特定したとき、別の列の値を1つに特定できる」という)対応づけができればよいというだけのことであり、それぞれの値が発生する順序や相関関係、客観的な因果関係とは区別すべきなのも事実です。
 やはりテーブルがあった方がわかりやすいので、次のようなテーブルがあるものとします。

地震テーブル
地震ID発生日震源地マグニチュード倒壊家屋数主な前兆現象
12004/9/5高知県沖4.10うろこ雲
22004/12/12宮崎県内陸5.51深夜に発光現象
32005/6/29和歌山県沖4.11ラジオにノイズ
42007/5/5広島県内陸6.73カラスが異常な鳴き声
52009/11/13高知県沖7.57湧水量減少
62010/8/20鹿児島県沖3.70下駄の鼻緒が切れた

 観測された地震の状況を記録したものですが、地震IDは1件ごとの地震に割り振られた番号ですので自ずと

地震ID→{発生日,震源地,マグニチュード,倒壊家屋数,主な前兆現象}

となります。
 さて、主な前兆現象という列ですが、慎重な立場から見ると「下駄の鼻緒が切れたとか何とかはただの偶然で無関係じゃないのか。」などと言いたくなるところかもしれません。しかし、このテーブル作成者は何かしらの前触れがあったと考えて記録しているわけで、それが主観であろうと非科学的であろうと意味を持って地震と身の周りの現象(下駄の鼻緒その他)を関係付けしていることから、現実にテーブルになっていますし、関数従属性として

地震ID→主な前兆現象

が成立しています。
 さて次に、マグニチュードと倒壊家屋数を見ると両者には関連性がみられ、マグニチュードが大きいほど倒壊家屋数が大きい傾向にあります。確かに、地震のエネルギーが大きいほど多数の家屋の倒壊につながるというのは間違いのなさそうな事実です。では、そのことををもって

マグニチュード→倒壊家屋数

という関数従属性が成立しているといえるのでしょうか。答えはノーで、これは成立していません。震度の値を1つに特定しても、それに対応した倒壊家屋数の値を1つに特定することはできないからです。

 ちょっと細かくなりますが、関数従属性についてあまり深く考えてしまうと、例えば「販売日→販売曜日(実例:平成27年1月5日は月曜日)というのは明らかなので理解できるが、社員番号→氏名(実例:社員番号が1の社員の氏名は竹田博和)というのは論理の飛躍ではないか」などという気がしてしまうかもしれません。確かに販売日と販売曜日の関係や摂氏温度と華氏温度の関係はどこの誰が計算しても同じ結果となる(導出可能といいます)一方、社員番号と氏名の関係は会社ごとにまちまちです。しかし、それぞれの会社という世界において成り立つ立派な関数従属であって、そうしたローカルな情報だからこそ個別にテーブルとして整理しておく必要があるわけです。
 一方、販売日→販売曜日のような導出可能な関係は、関数従属の特殊ケースといえます。また、むしろその明確な関係ゆえに両方の値をテーブルに記録していく必要性はありません(販売日さえ記録しておけば計算により曜日を導くことが出来ます)。

候補キー・非キー属性・スーパーキー

 関数従属性について簡単に見てきましたが、ここで改めてテーブルのキー(ここでは、行を特定できるような列のことを指します)との関連について触れておきたいと思います。細かい議論となりますが実例を慎重に検討してみてください。

 あるテーブルにおいて列Aの値によって行を1つに特定できるというのは、(全く同じ行が存在しないということを前提として)列Aの値を1つに特定したときに、列A自身はもちろんのこと、他の列もすべて列Aに関数従属しているということです(なお、列Aは必ず列Aに関数従属します。列Aの値が1つに定まれば列Aの値は1つに定まるという、当たり前の理由です)。
 上記の社員テーブルを再掲します。

社員テーブル
社員番号氏名住所
1竹田 博和○○市☆☆2−2
2井上 美咲□□市**6−12
3竹田 博和××町□□6−9
4国府 伸一○○市△△3−1
5松嶋 優斗◇◇区◎◎4−6

このテーブルでは、社員番号によって行を1つに特定できます。これは、社員番号の値を1つに特定したときに氏名と住所の値を1つに特定できるということ、つまり、社員番号→{氏名,住所}が成り立つということです。一方、氏名や住所によって行を特定することはできません。つまり、氏名→{社員番号,住所}も、住所→{社員番号,氏名}も成り立っていないということです(住所が同じ社員がいないので、住所により行を特定できるように見えますが、そうはいえません。詳しくは上記の内容を参照してください)。
 ところで、社員番号と氏名の組み合わせによって行を1つに特定できる、というのも事実です({社員番号,氏名}→住所)。また、社員番号と住所の組み合わせによっても行を1つに特定できます({社員番号,住所}→氏名)。さらには、社員番号と氏名と住所の組み合わせによっても行を1つに特定できます({社員番号,氏名,住所}→{社員番号,氏名,住所})。しかし、これらの事実はあまり重要ではありません。なぜなら、社員番号の値さえ特定できれば行を1つに特定できるからで、そこに住所やら氏名を組み合わせても余分なだけだからです。
 この場合の社員番号のように、その列の値さえ特定できれば行を1つに特定できる、という(余分な列を含まない最小限の)列のことを候補キーといいます。また、氏名や住所のように、候補キーに含まれない列のことを非キー属性といいます。{社員番号,氏名}や{社員番号,住所}のように、行を1つに特定できるものの余分な列を含んでいる組み合わせのことをスーパーキーといいます。名前にスーパーと付くので何か凄そうな気がしますが、実用上は候補キーの方が圧倒的に重要であり、スーパーキーに大した意味はありません。
 候補キーは複数の列からなる場合もあります。上記のクラス名簿の例がそうですので再掲します。

クラス名簿
クラス番号出席番号氏名
11藤井 美優
12平本 和之
13大西 遥
14長澤 麻友
21秋元 大樹
22益岡 泰三
23照井 桃子

クラス番号と出席番号の両方さえ特定できれば行を1つに特定できます。しかし、クラス番号だけ、あるいは出席番号だけでは特定できません。よって、このテーブルの候補キーは{クラス番号,出席番号}です。氏名は候補キーに含まれないので非キー属性です。
 また、候補キーは1つのテーブルに複数存在する場合もあります。

自動車テーブル
ナンバー車台番号車種所有者
品川300お11-11AB11C99999-1111111ヴィッツ斉藤 薫
湘南500し12-34ZZAA1234-98BBYY777RX-7関本 陽一
横浜300へ77-77XYZ753QQ-77PP45678マークX杉山 真季
世田谷500ん99-9999999AB11C-8888888デミオ小泉 遥人

これも主キーの説明で触れた同じテーブルの再掲ですが、1行ごとに1台の自動車を記録しています。一般に自動車のナンバーは重複しませんし、車台番号も重複しませんので、どちらか一方だけでも特定できれば1台の自動車を特定できる、つまり行を1つに特定することができます。よってナンバーは(単独で)候補キーですし車台番号も(単独で)候補キーです。車種も所有者も非キー属性です。

主キーの再検討

 さて、主キーの節でも同じような説明をしましたが、候補キー、スーパーキーの知識を踏まえて主キーとの関係を整理しておきます。
 まず、候補キーやスーパーキーというのは、それぞれの列を他の列との関係に基づいて分類したものであり、Accessというソフトの存在以前の概念です。一方で主キーというのはAccessというソフトが持つ機能であり設定ですので、ユーザーが設定した列が(適当かどうかは別として)そのまま主キーとなります。その点をきちんと区別する必要があります。
 そして、主キーと候補キー、スーパーキーの関係としては、候補キーを主キーとして設定すべきです。スーパーキーを主キーにすべきではありません。ましてや、ただの非キー属性を主キーとするべきでないのは言うまでもありません(行を特定するという最低限の役割を果たせないからです)。
 実際に、クラス名簿

クラス名簿
クラス番号出席番号氏名
11藤井 美優
12平本 和之
13大西 遥
14長澤 麻友
21秋元 大樹
22益岡 泰三
23照井 桃子

を例にして設定してみましょう。
 候補キー{クラス番号,出席番号}を主キーにすることにより、当然ながら候補キーの値の重複が許されなくなります。

(図)

重複してしまうと、候補キーの値により行を1つに特定するということができなくなりますので、これは適切な制約です。実際に、想定どおりの制約がかかっていることがわかります。

(図)

 しかし、スーパーキー{クラス番号,出席番号,氏名}を主キーにしてしまうと、スーパーキーの重複こそ許されないものの、肝心の候補キー{クラス番号,出席番号}については重複が許されることとなってしまいます。

(図)

クラス番号と出席番号がともに重複する行があってもエラーとなりません。これでは不適切です。
 主キーを単に「設定しておけば全く同一の行が発生しないので、入力に支障のないように設定さえしておけばよい」というように捉えてしまうと、このような不適切な主キーの設定につながります。極端にいえば、常に全ての列を選んで主キーにしてしまおう、という考え方にもつながりかねません。しかし、簡潔に行を特定できるようコードを設定するという一般的な方針に従う以上、候補キーをきちんと識別した上でそれを主キーとして設定するようにしなければいけません。

 なお、候補キーが複数存在する場合は、そのうち1つを選んで主キーとすべきです。両方を選んで主キーにしてしまうと複合キーとなってしまい、適切な制約となりません。

関数従属性のいくつかの類型

 関数従属性の例はさまざまであり、かなり複雑なケースも考えられますが、重要ないくつかの類型がありますので、これらについて説明します。同じような議論の繰り返しになりますが、ここまで理解できれば、正規化を理解する準備が整ったものといえます。

完全関数従属

 ある関数従属

{A,B}→C

が成り立ち、かつ、

A→C
B→C

のいずれも成り立たない場合、「Cは{A,B}に完全関数従属している」といいます。
 印象としては、{A,B}→Cが成り立ち、かつ、A→CもB→Cも成り立っているのを「Cは{A,B}に完全関数従属している」と表す方が自然に思われるかもしれません。しかし実際は、{A,B}→Cが成り立ち、かつ、A→CもB→Cもどちらも成り立たないのを「Cは{A,B}に完全関数従属している」といいます。なぜでしょうか。
 まず、A→Cが成り立つなら必ず{A,B}→Cも成り立つということを理解する必要があります。例として、

社員番号→住所

であれば、必ず

{社員番号,氏名}→住所

が成り立ちます。社員番号さえ特定できれば住所を特定できるのですから、社員番号と氏名の両方が特定できたときに住所が特定できるのは当たり前です。このように、A→Cが成り立つなら必ず{A,B}→Cも成り立ちます。同様に、B→Cが成り立つなら必ず{A,B}→Cも成り立ちます。つまり、A→CまたはB→Cが成り立てば{A,B}→Cが成り立つのは当たり前なのです。
 さらに例を挙げると、

{社員番号,血液型}→住所
{生年月日,社員番号}→血液型
{社員番号,氏名}→所属部署

といったように、列数がちょっと多いテーブルであれば{A,B}→Cという関係はいくらでも存在します。このことから、{A,B}→Cが成り立つ場合、たいていはA→BまたはA→Cが成り立っていることが推測されます。
 しかし、{A,B}→Cが成り立っているのにA→CもB→Cも成り立っていない、というのは当たり前のことではありません。そこで、特に注目すべきもの、つまり「完全関数従属」として分類しているのです。
 完全関数従属の実例としては上記のクラス名簿の例がわかりやすいですので、またも再掲します。

クラス名簿
クラス番号出席番号氏名
11藤井 美優
12平本 和之
13大西 遥
14長澤 麻友
21秋元 大樹
22益岡 泰三
23照井 桃子

この例では「クラス番号と出席番号の両方を特定できてはじめて氏名を特定できる」ということから、

{クラス番号、出席番号}→氏名

が成り立ちます。しかし、

クラス番号→氏名
出席番号→氏名

のいずれも成り立っていませんので、氏名は{クラス番号,出席番号}に完全関数従属しているということができます。

部分関数従属

 ある関数従属

{A,B}→C

が成り立つとして、

A→C
B→C

のいずれかが成り立つ場合、Cは{A,B}に部分関数従属しているといいます。完全関数従属と対になる分類であり、比較することで理解しやすくなります。実例として、上記のクラス名簿と似た次のテーブルがあるものとします。

クラス名簿'
クラス番号クラス名出席番号氏名
1茶道1藤井 美優
1茶道2平本 和之
1茶道3大西 遥
1茶道4長澤 麻友
2生け花1秋元 大樹
2生け花2益岡 泰三
2生け花3照井 桃子

 クラス番号と出席番号、氏名の関係は上記のクラス名簿と同様ですので、氏名は{クラス番号,出席番号}に完全関数従属している、ということになります。
 一方で、クラス名について見てみましょう。クラス番号と出席番号の両方が特定できていれば、クラス名は特定されます。よって、

{クラス番号,出席番号}→クラス名

が成り立ちます。しかし、そもそもクラス番号と出席番号の両方を特定しなくとも、クラス番号さえ特定できればクラス名はわかります。つまり

クラス番号→クラス名


も成り立っています。これらのことから、クラス名は{クラス番号,出席番号}に部分関数従属している、ということになります。

推移的関数従属

A→B
B→C

がいずれも成り立ち(これをA→B→Cと表します)、かつ

B→A

が成り立たないとき、CはAに推移的に関数従属しているといいます。
 例として、正規化の実例の節で現れた以下のテーブルを再掲します。

販売テーブル
販売ID取引先コード取引先名取引先所在地
11山田商事○○区△△
22斉藤商店◇◇町☆☆
31山田商事○○区△△
43竹田ストア**市□□

 1件の販売について1つの販売IDを付し、その販売先情報とともに整理しているテーブルです。
まず、販売IDが特定されれば取引先コードが1つに特定されますので

販売ID→取引先コード

が成り立っていることがわかります。
 また、(販売IDが特定されなくとも)取引先コードさえ特定できれば取引先名、取引先所在地は1つに特定されます。よって

取引先コード→{取引先名,取引先所在地}

が成り立っています。一方で取引先コードを特定しても販売IDは1つに特定されません(1つの取引先に複数回販売されることがあるからです)ので、

取引先コード→販売ID

は成り立ちません。
 以上より、{取引先名,取引先所在地}は販売IDに推移的に関数従属をしているということになります。

 では、またも再掲ですが、次のテーブルに推移的関数従属は存在するでしょうか。

自動車テーブル
ナンバー車台番号車種所有者
品川300お11-11AB11C99999-1111111ヴィッツ斉藤 薫
湘南500し12-34ZZAA1234-98BBYY777RX-7関本 陽一
横浜300へ77-77XYZ753QQ-77PP45678マークX杉山 真季
世田谷500ん99-9999999AB11C-8888888デミオ小泉 遥人

まず、

ナンバー→車台番号
車台番号→{車種,所有者}

が成り立っており、つまりは

ナンバー→車台番号→{車種,所有者}

が成り立っています。しかし、

車台番号→ナンバー

でもあるため、{車種,所有者}がナンバーに推移的に関数従属しているとはいえません。同様に、{車種,所有者}が車台番号に推移的に関数従属しているともいえません。
 結局、このテーブルに推移的関数従属は存在しません。