« 主キーはインデックスではない | トップページ | ドキュメントの山で遭難しないために »

2014.09.03

ユニーク制約の使いどころ

 前回記事で説明したように、主キーは「ユニーク制約をともなうインデックス」ではなく、「本来的なユニークキー(一次識別子, Primary Key, PK)」である。すなわち、「NULL値をとらないし、レコードのライフサイクルにおいて値が更新されないユニークキー」だ。テーブルはユニークキー(候補キーともいう)をいくつも持ち得るが、その中で少なくとも1個は主キーとして位置づけられる必要がある(*1)。

 「本来的なユニークキー」はわかった。では「本来的でないユニークキー」とはどんなものか。その使いどころや効果的なテクニックについて説明しよう。

 まず次のモデルを見てほしい。主キーが「顧客コード」であることが<...>で示されている。これに「顧客名+所在地」の組み合わせでユニーク制約({...}で示してある)を与えた例だ。同名顧客名があり得るため、SKには「所在地」も含めてある(顧客名や所在地は正規表現されているとする)。

[顧客] <顧客コード>,{顧客名,所在地},与信額,...

 これが「本来的でないユニークキー」、すなわち「二次キー(二次識別子, Secondary Key, SK)」の典型的な使い方である。主キーと違い、二次キーの値はレコード登録後に変更されることが許される。つまり顧客名や所在地の値は変わることがある。

 ここで、「顧客コード」をSKとして位置付けるテクニックを紹介しよう。モデルは次のようになる。顧客コードに代わって、無意味な順序番号として自動発番される「顧客id」がPKとして置かれている。顧客コードはPKでないので、その値は変更可能である。

[顧客] <顧客id>,{顧客コード},{顧客名,所在地},与信額,...
    00256  YOSHIMOTO   吉本興行 ...
    01234  YOSHIMOTOSAN 吉本産業 ...

 なぜわざわざ顧客コードの値が変更されることを認めるのか。上の例のように、それまで"吉本興行"向けに"YOSHIMOTO"のコードが設定されていたのだが、あらたに"吉本産業"のレコードが追加されたので、既存の"YOSHIMOTO"のコードを"YOSHIMOTOKOU"等に変更したくなるかもしれない。そのために、「顧客id」を「ユーザに不可視なPK」として導入したうえで、顧客コードを「変更可能かつユーザに可視的なSK」とするのである。

 この場合、顧客コードは他テーブルにおいて参照キー(外部キー)として構成されてはいけない。次のようにあくまでもPKにもとづいて設定される必要がある。参照先で値が変わり得るような項目に依拠してテーブル関連が作られてはいけないからだ。

[顧客] <顧客id>,{顧客コード},{顧客名,所在地},与信額,...
+   01234  YOSHIMOTOSAN 吉本産業 ...

└―…[受注] <受注№>,顧客id,受注日,...
       J56789 01234  09/10 ...

 受注エントリーのUIでは、顧客を特定するための手がかりとして顧客コードが用いられることになろう。しかし、いったん顧客が特定されたなら、顧客コードではなく顧客idにもとづいて受注レコードは登録される。データの取り回しが多少面倒になるうえ、生のレコードを見ても関係がわかりにくいといった問題があるが、上述の効果とのバランス次第で利用できるテクニックである。

 同様の調整は商品テーブル等でも応用できる。ただし、顧客コードや商品コードの値を頻繁に変更すれば、現場での混乱が避けられない。だからこれをやるとしても「いざというときには変更できる安心感」のためにSKとしておく、と考えたほうがいい。

 なおこの場合、そもそも「顧客コードは要らない」と考えても間違いではない。顧客レコードの件数が多くて、かつ、何度も注文してくるなじみの顧客がないとすれば、顧客コードを手がかりにして顧客を特定する必要はない。顧客コードでなく常に顧客名を手がかりにして探せるようであればいい。そうなれば、顧客コードのような項目は不要である。

 SKに関する他の使いどころとして、私が「汎用アクセスキー」と呼んでいるテクニックを紹介しよう。

 たとえば、売上実績(売掛増減履歴)や受払実績(在庫増減履歴)といった伝票系データがあるとする。これらの計上のもとになるトランザクションテーブル(出荷明細、入荷明細、在庫移動等)がいろいろある。それらに「取引ソース№」のような汎用的なSKを置く。それらはさまざまな様式の主キーを持つはずだが、それらにシステム内で統一されたSKを置くことで、実績テーブルは統一的な参照キーを使って「計上処理の元ネタデータ」にアクセスできるようになる(この場合、SKといえども値が変更されることが許されない点に注意)。DB構造がスッキリするのでお薦めしたいテクニックだ。

 以上は「積極的」なユニーク制約の使いどころだったが、次に「消極的」な使いどころを紹介しよう。全テーブルの主キーを"ID"とする、いわゆる「ID方式」に関係する話である。たとえばを主キーとするテーブルを実装する際に、意図的にサロゲートキー(代理キー)を導入することがある。

[ABC] <a,b,c>,d

↓サロゲートキー(e)を導入

[ABC] <e>,a,b,c,d

 トランザクション系テーブルの設計では、このテクニックがよく用いられる。複合主キーを単独主キーに置き換えることで、データの取り回しが楽になるからだ。ただし、この調整はあくまでも「正規化崩し」の一環なので、その「報い」を引き受けることを忘れてはいけない。すなわち、テーブルは次のように処置されなければいけない。

[ABC] <e>,{a,b,c},d //ただしa,b,cは変更不可//

 もともとのPKであった{a,b,c}についてユニーク制約を付与するとともに、属性項目として位置付けられたそれらの項目について値が更新されないことを保証しなければいけない。ユニーク制約についてはモデルで示したようにSKを組み込めばよい。ところが、更新不可制約については個々のアプリで対処するしか手がない。以上がこの場合の「正規化崩しの報い」である。

 残念なことに、ここらへんが仕様化・文書化されて保守担当者に申し送られることが徹底されない。結果的に、サロゲートキーを多用するスタイル(ID方式)は、保守フェーズでじわじわとデータが濁ってゆくがその原因が不明という厄災をもたらす。

 大事なのは、サロゲートキーやID方式を用いるのであれば、複合主キーを含んだオーソドックスなデータモデリングを事前にして完了しておくということだ。適切なモデルを慎重に正規化崩しするからこそ安全な実装仕様が手に入る。いきなり最初からID方式で設計すれば、正規形を経由できないゆえに、必要な報いを仕様化することに失敗する。なにしろ設計者には正規化崩ししている認識さえない。正規化の手順が面倒だとか、正規化崩しにともなう辛気臭い報いを引き受けることが面倒だというのであれば、サロゲートキーやID方式については敬して遠ざけたほうがいい。

 ようするにここらへんのテクニックは上級者向けのものであって、初心者が手を出せば火傷で済まない事態を招く。ところが困ったことに、ID方式やこれを強制するフレームワークはひどく取っつきやすそうに見える。そのため、「関数従属性だの正規化だのといった小理屈よりも、いかに顧客に俊敏に価値をもたらすかが重要」的なノリの開発現場で安易に採用され、いたずらに問題を生じさせている。顧客のためと言うのなら、けっきょくは「急がばまわれ」であることを知ってほしい。

 じっさいは、テーブル数が10個程度であれば、ID方式だろうが無手勝流だろうがたいした違いは出ない。しかし、数十個以上のテーブルを含むような案件を相手にする可能性があるのなら、あらかじめデータモデリングのスキルを身につけておいたほうがいい。その必要性は、ID方式を強制するフレームワークを利用するかしないかには関係ない。


*1.履歴系のテーブルなどでは、他のテーブルとの参照先になり得ないために主キーを付与する必要性が感じられないケースがある。しかしその場合でも、一覧上の特定レコードを処理することがあり得るため、ひととおりのテーブルについてPKを置いたほうが無難である。

|

« 主キーはインデックスではない | トップページ | ドキュメントの山で遭難しないために »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック


この記事へのトラックバック一覧です: ユニーク制約の使いどころ:

« 主キーはインデックスではない | トップページ | ドキュメントの山で遭難しないために »