[DBデザイン#39] クラス図でビューを記述する方法

ER図やクラス図で、テーブル設計を記述することはよく行われています。その結果をもとに、CREATE TABLE文を駆使して、テーブル定義のためのSQL文を記述し、データベースに読み込ませて使えるようにします。文法的な間違いは随時直すとして、設計の変更などが発生してフィールドを追加するなど、データベースの変更は開発中には付き物です。ということで、モデルとテーブル定義のSQL文が分離した瞬間、両方をメンテナンスするということになり、変更を適用するための記述はSQL側になるので、やはりモデルの修正はおろそかになってしまいます。常に、モデルから生成できればいいのですが、データベース特有の記述を毎回手で治すような事態だと、別々にしているのと手間は変わりません。ただ、最近のWebアプリケーションフレームワークは、モデルをコードで記述することで、自動的にデータベースの生成や、あるいは設計が変わった時のデータの移行なども含めてやってくれるので、その意味ではモデルベースではありますが、ERやクラス図と、モデルのコードの変換や対応付というまた同じ問題に直面します。この辺り、なかなか良い方法はないというか、労力をかけないで、ドキュメントをメンテナンスするということへの工夫をとにかく頑張って凝らさないといけないところではあります。ERやクラス図を最初に作り、その後、SQLやコードなど実装が進むと、ER/クラス図が放置されるのが常かもしれませんが、開発が落ち着いたら、実情と合うようにモデルを修正しておくようにしたいところです。

実際に開発を始めると、テーブルだけでなく、ビューも使います。テーブルのやりとりだけだと、何度もアクセスが必要になります。そこで、SELECT文でいくつかのテーブルを結合した結果が欲しくなるのですが、ソースコードにSELECT文を展開する場合もあるかもしれませんが、ビューを定義して、あたかも「1つのテーブル」のように扱える仕組みは便利です。1つのビューをいくつかの目的に使い回しもできます。ビューを最初から用意するというのが1つの理想的な状況ですが、現実には開発をしながらビューを追加することが多いと思われます。つまり、実際にユーザインタフェースのコードを記述するときに、「このページにはこのフィールドがいるけど…」と検討を始めて、「このテーブルと、あのテーブルと、そちらのテーブルを結合しないといけないな」などを考えて、CREATE VIEW AS SELECT …などと書いていることが多いと思われます。実際にUIのコードと対峙しないと、必要なビューというのが見えてこないというか、気合を入れて考える気がしないのではないでしょうか。そして、フィールドが足りないとビューの定義を書き直して、DROP VIEWしてまたCREATE VIEWをするという手順でメンテナンスを繰り返します。テーブルを変更するときはALTER TABLEコマンドを使わないと、DROP TABLEしたらテーブルが消えてしまいます。ですが、ビューは、一時的にはどこかに結果はあるとしても、設計情報だけが必須のものなので、DROPしてCREATEできる点では気軽に変更できます。

気軽に作成した結果、何が起こるかというと、ありがちなのは似たようなビューをいくつも作っていたり、名前の付け方が統一されていなかったりといったことが起こりがちです。動いていればいいという考え方もありますが、できれば、後からのメンテナンスをやりやすいように構成されていたいところです。ということで、ビューも、テーブル設計に続いて設計内容として記述できればと思ってしまいます。ER図作成ソフトの中にはER図にビューという要素をエンティティの1つとして配置できるものもありますが、結果的に構造がテーブルと同じということで、テーブルの代わりに置けなくもないという感じの機能です。むしろ、ER図ではビューは記述しないのが一般的と言えるかと思われますが、実のところ、この理由を記載した記事などはみたことがありません。一番考えられるのは、ビューの設計は、SQL文でテキストで記述してしまうので、それを確認するのがとにかく確実で早いということがあるからではないかということです。当然、そこにはビューの設計情報が全部あり、テキストなので検索も確実に引っ掛かります。しかしながら、テキストファイルに大量にビューの定義があると、前に説明したように、同じような別のビューを定義してしまうということにもつながります。開発で頭が爆発しそうな時でも、冷静に既存のビューを参照できるようにするために、クラス図にビューも記述するのが良いのではないかと考えて若干、実践をしてみましたが、ただ、やり始めてあまり時間が経過していないので、効果の程はまだ未知数です。前回に出てきたテーブル設計のクラス図が上半分の6つのクラスにあるものと同様です。新たに、ビューとして2つ定義したことを書き加えました。ビューは下の2つです。まず、ビューの名前には、<<view>>というステレオタイプを記述し、通常のテーブルと区別できるようにしました。ついでに色分けもしてあります。

ビューが定義されている状態の何を知りたいのかということですが、まずは、どのテーブルあるいはビューをもとにしているのかという情報かと思います。つまり、FROM句に並ぶテーブルです。販売明細テーブルの内容に、商品の情報をくっつけた「販売情報_商品付」というビューが定義されています。このビューは、販売情報と商品の2つのテーブルをもとに作られていることが分かります。左側のビューとも繋がっていますが、この線は、左側のビューが「販売情報_商品付」をもとに作られているものですので、ビューとビューを結ぶ線はどちらが大元なのかをチェックする必要があります。「販売情報_商品付」は、基本的に「販売明細」にあるレコードが、そのままビューの側にも存在するということを期待しています。つまり販売明細テーブルの拡張版なのです。そこで、黒いダイア付きの線、コンポジションを利用して、「販売情報_商品付」が販売明細と同一のレコード構造、つまり、販売明細の1レコードが、「販売情報_商品付」の1レコードになることを示唆しています。コンポジションの意味から若干外れるのですが、販売明細の1つ1つのレコードをパーツとして、それをそのまま組み立てているというイメージで、当たらずとも遠からずかと思われます。そして、商品からの線は、「販売情報_商品付」ビューが商品とも結合されていることを意味してますが、通常の結合でない場合、ここでは左結合の場合は、<<left-join>>というステレオタイプをつけておきます。ここで、コンポジションに当たる販売管理に対して、商品が左結合するということになります。そして、属性のところには存在するフィールド名を記述します。主キーを明示したい場合は、ステレオタイプの<<PK>>などを記述しても良いでしょう。そして、最後の「金額」フィールドは計算フィールドです。計算フィールドの名前の前に / が付けられていますが、派生と呼ばれる記号で、他のフィールドの値から導出可能であることを示しています。もちろん、この場合はさらに同一レコードの値から導出可能であるので、派生の記号のまさに使い所です。式はどうするか、ここではメモに記述していますが、左側のビューのように、制約として記述する方法もあります。長くなると、メモの方がみやすくなるかもしれません。最も、ここに式をあまりに細々と記述してみづらくなるのであれば、式はいっそのこと省略して、CREATE VIEWを参照するということでも良いでしょう。

「納品書_合計付」ビューは納品書の1レコードが、ビューの1レコードになることを期待しているので、納品書テーブルからの線はコンポジションとなります。そして、顧客名などを取り出したいので、顧客テーブルを左結合します。しかしながら、ここで、明細の合計を取りたいのですが、「販売情報_商品付」ビューから値を取ってきて合計を取ります。まさに、「販売情報_商品付」ビューの結果を集約するので、白いダイアマークのアグリゲーションが適切です。ただ、この時、どのフィールドでグループ化するのかもステレオタイプ<<group-by>>で示しておきます。そして、合計金額の派生フィールドが、SUM関数を使った式を持っているので、そこに関連した明細の「金額」フィールドの合計の値が求められることになります。

以上のビューを実際のSQL文で記述するとこんな感じです。なお、このままのSQL文は通らないかもしれません。アグリゲーションがある場合、アグリゲーションに関係ないフィールドをSELECT句に入れてはいけないというルールで運用されている場合が最近は増えています。

CREATE VIEW 販売情報_商品付 AS
SELECT 販売明細ID, 納品書ID, 商品ID, 商品名, 単価, 個数,
  単価 * 個数 AS 個数
FROM 販売明細
  LEFT JOIN 商品 ON 販売明細.商品ID = 商品.商品ID

CREATE VIEW 納品書_合計付 AS
SELECT 納品書ID, 顧客ID, 顧客名, 販売日,
  SUM(金額) AS 合計金額,
  SUM(金額) * 0.1 AS 消費税額,
  SUM(金額) * 1.1 AS 総計
FROM 納品書
  LEFT JOIN 顧客 ON 納品書.顧客ID = 顧客.顧客ID
  INNER JOIN 販売情報_商品付 ON 納品書.納品書ID = 販売情報_商品付.納品書ID
GROUP-BY 納品書.納品書ID

ビューとビューの間の線が、コンポジションでもアグリゲーションでもない場合は、どちらが元になっているのか分かりにくいので、この例では記述がありませんが、もとになっている側に矢印の矢尻、つまり、導出可能の記号を付けておくのが良いでしょう。最も、矢尻はコンポジションやアグリゲーションの記号の反対側なので、常につけるでもいいような気がします。

図に、テーブル間結合の式は必要かというと、その式は、大まかにはすでにテーブル間結合の図で記述されています。どのテーブルを持ってくるのかということから、関連のための条件式はほぼ決まります。最も、リレーションシップを検索のように使う場合もあるので、そのようなテーブル間の関係性として定義していないリレーションシップでの結合を行う場合は、結合の線に対してメモで条件を記述すると良いでしょう。

実際案件で、ビューも一緒に記述してみました。やはりというか、線だらけになってしまいました。なので、ビューへの結合線は薄い色にするなど、本来のテーブル間の関連付けの線を目立つようにしています。ですが、ほとんど同じビューが2つあって(これは意図的なんですが)、そういった定義でちょっとだけ違うのがどこなのかは一目瞭然になります。ただ、やはりというか、1つ1つのビューの用途については、箱の存在だけではピンとは来ません。そこで、メモでビューの用途や意図を記述をする必要性はあります。最も、これはテーブルについても同様かもしれません。

クラス図にビューを記述することは、前回紹介したAmbler先生のページにもありますが、今回紹介した記述方法はいくつかルールを追加しています。ビューも設計のうちですし、管理したいということもあるかと思いますが、テーブルの設計と併せて記述するのはあまり支持してもらえないかもしれません。みづらくなるだけだという感想を持たれるかもしれません。一方で、離れていた開発から戻った時や、メンテナンス開発の時などは状況の把握には有効でしょう。結局、その案件の中では、テーブルとビューを両方記述しましたが、両方記述した図と、テーブルだけを記述した図の2通りの出力を出すことにしました。