SQLer 生島勘富 のブログ

RDB・SQLの話題を中心に情報発信をしています。

サロゲートキーは後付けでもできる。

 業務システムのほとんどはナチュラルキーで構築されていると思います。
 しかし、シノニムやトリガーを利用すれば、既存システムを変更することなくサロゲートキーを追加して、それ以降、サロゲートキーによる運用も可能になります。手順は以下の通りです。

元のテーブル

 以下の3つのテーブルをサロゲートキーによる運用に変更します。

■ Foods 料理マスタ





物理名論理名備考
CD料理CD主キー
Name名前 
Price価格 
……

■ Ingredients 材料マスタ





物理名論理名備考
CD材料CD主キー
Name名前 
Cost_Price材料費 
……

■ Recipes レシピマスタ



物理名論理名備考
Food_CD料理CD主キー(複合)
Ingredient_CD材料CD主キー(複合)
Quantity使用量 
……

IDカラムを付加する。

 現在のテーブル名にIDを採番したシノニムを作成し、全部のテーブルにIDカラムを追加し、現在の主キーをユニークインデックスに変更する。
 IDカラムにDefualt値を設定し、主キーを変更する。

■ (M000_)Foods 料理マスタ ※ M000_Foods というシノニムを作る。

物理名論理名備考
IDID自動インクリメント(新主キー)
CD料理CDユニークインデックス
Name名前 
Price価格 
……

■ (M100_)Ingredients 材料マスタ ※ M100_Ingredients というシノニムを作る。

物理名論理名備考
IDID自動インクリメント(新主キー)
CD材料CDユニークインデックス
Name名前 
Cost_Price材料費 
……

■ (M200_)Recipes レシピマスタ ※ M200_Recipes というシノニムを作る。




物理名論理名備考
IDID自動インクリメント(新主キー)
Food_CD料理CDユニークインデックス(複合)
Ingredient_CD材料CDユニークインデックス(複合)
Quantity使用量 
……

外部キーを追加し、トリガーを作成する。

 元の外部キーに対応するサロゲートキー(テーブルID+_ID)を追加し、トリガーを作成する。

■ (M200_)Recipes レシピマスタ

物理名論理名備考
IDID自動インクリメント(新主キー)
M000_ID料理ID追加
Food_CD料理CDユニークインデックス(複合)
M100_ID材料ID追加
Ingredient_CD材料CDユニークインデックス(複合)
Quantity使用量 
……

 外部キーにあたる、ID(サロゲートキー)CD(ナチュラルキー)のいずれか片方だけに値が設定されているとき、設定されていない方を自動更新するトリガーを作成する。

 両方に値がセットされているときは、何もしない(アプリ側で整合性を取る)か、IDを優先するかをプロジェクトで決め、決めたとおりトリガーを作成する。

 マスタテーブルのCD(ナチュラルキー、元の主キー)が変更されたとき、対応するカラムのナチュラルキーをすべて変更するトリガーを作る。

トリガー・シノニムは非常に便利

 トリガーとシノニムを巧く利用すれば、既存のシステムは変更なしに移行が終わります。つまり、サロゲートキーが後から追加されても、すべてのナチュラルキーが非正規化されている状態になり、整合性がトリガーによって維持されているため、既存のアプリは変更する必要はありません。

 もちろん、追加開発をしたり、修正が入る度にサロゲートキーを利用するように促し、トリガーを追加することによる更新のレスポンスの劣化についてのチューニングも行う必要があります。

 ここまではナチュラルキーで設計されたDBをサロゲートキーに変更する手順ですが、実は私は、最初の設計からサロゲートキーとして作ったとしても、すべてのナチュラルキーを非正規化してトリガーで整合性を維持することを薦めています。

 というのも、サロゲートキーを使うとちょっとした抽出でも

 WHERE
   T100.XXXX_KBN = 'A'

       ↓

 WHERE
   EXISTS
     (SELECT * FROM M100
     WHERE
       M100.ID = T100.M100_ID
       AND M100.XXXX_KBN = 'A')

 といったサブクエリーが必要になります。

 これに耐えられない人が実に多い。「それぐらいやってくれよ」って思うこともあるけれど、自分でもめんどくさいと思うこともないこともない。しかし、これまで書いた方法と同じ構造を最初から作れば、整合性を保ったままナチュラルキーが非正規化されます。つまり、結合(FROM句)はサロゲートキーで、抽出(WHERE句)はナチュラルキーを使うというルールで運用することも可能になります。前にも書きましたが、もちろん、トリガーを仕掛けると更新のコストは高まりますが、日々行われている非効率な抽出に比べたら遙かに負担が小さいことが多いです。

 もし、更新の負荷が問題になるとしても、問題となるテーブル数は僅かでしょう。その僅かなテーブルに対してプロジェクトの特別ルールを設定してトリガーの処理を減らすようにすれば、ほぼ解決できます。

 しかし、「サロゲートキー&正規化原理主義」的なDBAが担当しているとき、とにかく抽出頻度が高いキーを非正規化するなどという中途半端な非正規化をするためには、非正規化するかどうか話し合ったり、依頼書が必要であったり、会議をしたりする必要が出てきます。しかし、全面的に画一的にやれば、そういった手間は必要ありません。トリガー・シノニムは、テーブル設計書を工夫して自動的に作ることが可能ですから、実際のコーディングはほとんど必要ありません。テーブル設計書のひな形さえ作っておけば単純作業です。更に、先ほどのサブクエリになるかならないかも統一できるため、ルールが決まれば品質の高いプログラムになります。

 というわけで、私も理想としては「ナチュラルキーの非正規化」はなしにしたいけれど、主キーはサロゲートキーで、ナチュラルキーは非正規化してトリガーで整合性を保つ、ということをお勧めしています。

 ここまでに使ったトリガー・シノニムは、テーブル設計書から自動出力できるようにすれば、元の設計で外部キーが正確に把握できる状態なら、負荷テストぐらいで移行が完了できます。更に、将来、コードの桁数が増えたり、コードの統一化など、コード体系の変更が発生しても、プログラミングレスで自動更新されます。もちろん、実際の変更処理については、処理時間などについての注意は必要ですけれど……。

 これらの変更は非常に効果が大きいのですが、費用がなかなかでないので、一部ずつは適用していますが、すべてを一度に適用できたプロジェクトはありません。余程、理解力のあるPM(かそれ以上)が決断しないとできないでしょう。

 しかし、本当に効果が出ますので是非チャレンジしてみてください。