咖啡日语论坛

 找回密码
 注~册
搜索
查看: 21954|回复: 7

データベースを基本から理解する

[复制链接]
发表于 2007-5-28 10:23:58 | 显示全部楼层 |阅读模式
回复

使用道具 举报

 楼主| 发表于 2007-5-28 10:24:34 | 显示全部楼层
3本柱で完全マスターする データベースの基本


 「データベース」と聞いてどのように思うでしょうか。「何か難しそうで自分には関係ないもの」と感じられる人もいるかもしれません。確かに,企業システムで使っている顧客管理データベースや売上管理データベースは大規模で複雑なものが多く,職業プログラマ以外の方にはとても手におえそうにありません。
 でも,データベースが活躍するのは,企業システムだけではありません。例えば,同窓会名簿など個人で使うちょっとしたアプリケーションも,データベースを使うことでずいぶん楽に作れるようになるのです。
 あるいは,「データベースだったら,年賀状作成ソフトについている住所管理データベースをずいぶん前から使っているよ」という方もいるかもしれません。そうした方はきっとデータベースの便利さを実感していますから,改めてご利益を説明する必要はないでしょう。この特集を読んで,データベースの中身や仕組みについての理解を深めてください。データベースを使った新しいアプリケーションのアイデアが湧くかもしれません。
ある決まりごとに従って格納したデータの集まり 最初に,データベースとは何か,ということについて説明しておきましょう。ある人は,「読んだ本の感想をテキスト・ファイルにどんどん書き込んで,“読書データベース”を作っているんだ」と言うかもしれません。しかしこうしたファイルはここでは,データベースと呼びません。
 データベースは,ある規則にしたがってまとめられたデータの集まりを指します。例えば,現在の主流であり,この特集で取り上げるリレーショナル・データベース(RDB:RelationalDataBase)は,すべてのデータをテーブルの形式でまとめたものです。テーブルについては,後で詳しく説明しますが,とりあえずここでは,2次元の表のようなものと考えておいてください。
 データベースは入れ物に過ぎません。データベースにデータを格納したり,格納してあるデータを検索するなどの管理を行うには専用のソフトウエアを使います。こうしたソフトウエアをデータベース管理システムと呼びます。
 RDBの例で説明しましょう。RDBには,複数のテーブルが含まれていて,テーブルのマス目にはそれぞれデータが格納されています。利用者や開発者は,リレーショナル・データベース管理システム(RDBMS:Relational DataBase ManagementSystem)を介して,RDBにアクセスし,データベース内のデータを検索したり,テーブルを作成したりします。
 データベース技術は奥が深いですから,すべてを理解しようとすると大変です。しかし,何ごとにも,ここさえ押さえておけば,とりあえずは大丈夫,という基本があります。RDBの場合それが,先ほど説明した「テーブル」,そして「正規化」「SQL」です。
 RDBでは,必ずテーブル形式でデータを格納しなくてはなりません。テーブルについて理解することは,RDBを理解するうえで不可欠なのです。さらに,データを素早く検索したり,格納したデータを効率よく保守するには,テーブルをうまく作る必要があります。その際に指針になる決まりごとが,正規化です。
 SQLは,RDBにデータを格納したり,検索したりする際に使う命令です。RDBでは基本的にすべての操作は,RDBMSにSQLを渡すことにより行います。SQLについて学習すれば,RDBのデータを自由自在に扱えるようになるのです。ここでは,これら3本柱について順番に,基礎知識と,それぞれの関連情報について説明していきます。
テーブルはすべての基礎 リレーショナル・データベース(RDB)では,データはテーブルと呼ばれる2次元の表に格納されます。テーブルは「行(row)」と「列(column)」で構成し,それぞれの列には名前(「列名」)が付いています。
 図1はある会社の社員の情報をテーブルにまとめたものです。それぞれの列には,どのような種類のデータを格納するのかを表す列名がついていて,各行に社員一人ずつの情報を格納します。そして,社員が増えるたびに新しい行として追加していくわけです。
図1●RDBのテーブルの構造
 テーブルでは,行と列を指定することにより,データを特定できます。例えば飯田高志さんの部署を知りたい場合,図1のテーブルの氏名の列で飯田高志さんを探し,飯田さんの行の部署名の列を見ると所属部署がわかるわけです。
 ここまで読んで,Excelなどのスプレッドシート・ソフトを使ったことがある人は疑問を抱くかもしれません。Excelでも,図1と同じような2次元の表を作れます。RDBのテーブルとExcelで作る2次元の表では何が違うのでしょうか。
 RDBのテーブルでは,データの検索を効率よく実行したり,データベース内容の不整合が発生しにくいように,スプレッドシート・ソフトとは異なる仕組みがいろいろ取り入れられています。ポイントは次の4点です。
ポイント1:各列は一意の名前を持つ RDBのテーブルでは,各列につける名前である列名が,ほかの列と重複してはならないという決まりがあります。実際,RDBソフトを使ってテーブルを作成するときに,列名が重複するとエラーが発生します。これを,「各列には一意な列名を付ける」などと言います。
 RDBでは列に対して条件を指定して,該当するデータを検索することがよくあります。例えば図1のテーブルで,性別が男である社員を検索するような場合です。RDBでは列名で列を区別しますから,列名がダブっているとどの列を検索すればよいのかわからなくなってしまいます。
 これに対してExcelの表では,同じ名前の列があってもエラーになりません。例えば,住所という名前を二つの列に付けて,一方には県名を格納し,もう一方には市町村名と番地を格納する,といったこともできます。
ポイント2:主キーが存在する RDBのテーブルは,該当する行をただ一つに決めることができる列を持っていなければなりません。例えば,図1のテーブルであれば,社員番号の列の値を決めると,対応する社員がただ一人に決まります(氏名では同姓同名がいますね)。該当する行をただ一つに決めることができる列を「主キー」,あるいは「識別キー」と呼びます。
 主キーは行をただ一つに決めるためのものですから,同一の値を持つ行が複数あってはいけません。加えて,すべての行は主キーに値を持っていなければなりません。すなわち,主キー(先の例で言うと社員番号)の値が入っていない行があってはいけません。ちなみに,値が入っていない状態のことを「NULL(ヌル)」と呼びます。
 複数の列を組み合わせて主キーとして扱うことも可能です。図1で言えば,もしこの企業に「一つの部署には同姓同名の社員を配属しない」という規則があって,それを将来にわたって厳守するとすれば,氏名と部署名を組み合わせて主キーとして扱うこともできます。例えば,「第1営業部の吉田正美さん」と言えば,対応する社員がただ一人に決まるわけです。組み合わせて主キーとして扱える列のことを,「連結キー」あるいは「複合キー」と呼びます。
ポイント3:テーブル間を関連付けられる 図1のテーブルでは,部署名が変更になった際に,該当するデータをすべて修正しなくてはなりません。例えば,第1営業部が海外営業部に名称を変更した場合,吉田正美さん(男)と福山友子さんの部署名をそれぞれ,海外営業部に直す必要があります。
 修正するのが数カ所程度ならいいですが,数百人規模の部署だったら大変な作業です。多くの行を持つテーブルにおいて,修正する個所は少なければ少ないほど良いと言えます。手間が省けるだけでなく,更新漏れによる不整合を防ぐことにもなるからです。
 そこで,データはできるだけ重複して持たせない,あるいは同じデータはできるだけ1カ所で管理するようにしたい,という要求が生まれます。RDBでは,複数のテーブルを作成し,それらの間に「関連(リレーションシップ)」を持たせることによって,こうした要求に応えられるようになっています。
 図2をご覧ください。各部署に対して部署コードを割り当てて,部署名を主キーとするテーブル(部署テーブル)を別に作成しました。さらに,図1のテーブルにおける部署名の列を部署コードに置き換えます(社員テーブル)。
図2●図1のテーブルを分割したところ。二つのテーブルには,部署コードを通じてリレーションシップ(関連)が付けられている
 こうしておけば,社員番号を指定したとき,両テーブルに共通して存在する列である部署コードを介して,部署テーブルから部署名を取得できます。部署名に変更があったときには,部署テーブルの対応する部署名を修正するだけで済みます。
 図2の部署テーブルにおいては,部署コードが主キーになります。一方,社員テーブルにおける部署コードは,ほかのテーブルの主キーになっていることから「外部キー」と呼びます。
ポイント4:制約を設定できる RDBではテーブルや列,また列と列の関連に「制約」を持たせることができます。制約とは,RDBの内容を整合性が取れた状態に保つことを目的として,テーブルに格納するデータに課す条件のことです。制約を設定することで,意図しないデータや間違ったデータの入力を防げるので,データベースを管理する際の手間を軽減できます。
 例えば,図2の主キーである社員番号の列に対しては,ポイント2で説明したように,「値が重複してはならない」「NULLであってはならない」といった制約があります。これらをそれぞれ,「一意制約」「NOTNULL制約」といいます。主キーに対するこうした制約はまとめて,「主キー制約」と呼ばれます。
 ほかに,氏名の列に対しては,値がNULLでは困るのでNOT NULL制約をつけておきます。また性別は男か女かのいずれかですから,それ以外の値の格納を許さない「CHECK制約」を付けるのが良いでしょう。
 外部キーである部署コードに対しては,「参照整合性制約」と呼ぶ制約を付けます。参照整合性制約とは,関連付けられているテーブルで主キーの値として登録されていない値は,その列に格納できないということです。
 図2の社員テーブルに新しいデータを追加する場合を考えてみましょう。もし何かのミスで存在しない部署コードを入力しようとした場合,参照整合性制約が設定されていなければそのまま格納されてしまいます。追加する社員は,部署テーブルに存在するいずれかの部署に所属するはずですから,これは明らかにおかしいことになります。
 外部キーである部署コードに参照整合性制約を設定しておけば,部署テーブルに存在しない部署コードを社員テーブルに追加できません。同時に,社員テーブルから関連付けされている部署テーブルの行を削除することもできなくなります。不整合を引き起こすような更新をあらかじめ防ぐことができるわけです。
知っておきたいキーワード
XMLデータベース
 リレーショナル・データベース(RDB)ではテーブル形式で表現したデータをデータベースに格納しました。データを表現する形式はテーブルに限りません。ここでは,XML(Extensible MarkupLanguage)形式のデータを格納するXMLデータベースについて紹介します。XMLデータベース自体は以前からあるのですが,最近になってXMLを利用するアプリケーションが増えてきたため,改めて注目されています。
 リストAは書籍のタイトル(title),著者(author),価格(price),発行年月日(date),記事(article)を記述したXML文書の例です。タグの入れ子構造によって,データの階層構造が表現されていることがわかります。
<?xml version="1.0"?>
<books>
  <book>
    <title>日経ソフトウエア11月号</title>
    <author>日経BP社</author>
    <price>980</price>
    <date>2004/09/24</date>
    <articles>
      <article>データベース</article>
      <article>Java</article>
    </articles>
  </book>
  <book>
    <title>日経コンピュータ9月6日号</title>
    <author>日経BP社</author>
    <price>980</price>
    <date>2004/09/06</date>
    <articles>
      <article>社員監視時代</article>
      <article>ザ・プロジェクト</article>
    </articles>
  </book>
</books>
リストA●XML文書の例
XMLデータをツリー構造で格納 XML文書をデータベースに格納するには2種類の方法があります。一つは,XML文書をそのまま扱えるネイティブXMLデータベース(以下,単にXMLデータベースと呼びます)を使うこと。もう一つは,XML形式のデータをテーブル形式に変換して格納するXML対応RDBを使うことです。
 図A(a)はXMLデータベースのデータ格納構造のイメージです。リストAのXML文書をツリー構造で格納します。
図A●リストAのXMLデータの格納構造。(a)XMLデータベース,(b)XML文書の要素や属性をレコードに対応付けて格納するRDB。ほかに,XML文書をテキストでRDBにそのまま格納したり,ツリーに展開してRDBに格納するタイプがある [画像のクリックで拡大表示]
 多くのXMLデータベースが備える特徴の一つは,スキーマ(データベース構造の仕様)の定義を必要としないことです。XML文書には,構造とデータの両方を記述しますから,スキーマなしでデータの種類を判別できるからです。データの構造を変更したり,項目を追加するときにいちいちスキーマを修正する必要がありません。
 RDBと比べたときの注意点として,XMLデータベースは更新処理を不得手としていることが挙げられます。更新の単位,ロックの範囲,トランザクション機能は製品によって様々であるため,導入の際には十分な検討が必要です。
RDBにXML形式のデータを格納する XML対応RDBの格納方法には大きく三つあります。一つは,XML文書をテキストとしてそのまま格納するタイプです。実装は簡単ですが,XMLデータベースが持つ柔軟性は持っていません。検索する場合も,XMLデータを取り出して解析する処理が必要になるため,パフォーマンスが悪くなりがちです。内部にインデックスを持たせることによってパフォーマンスを改善しているものあります。
 ニつ目はXML文書の要素や属性を,RDBのレコードに対応付ける(マッピングする)タイプです(図A(b))。テーブル形式で格納しますから,格納後はSQL文で操作できます。このタイプの問題点は,XML文書の構造やRDBの構造によってはマッピングが困難な場合があること,マッピング処理の負荷がかかることです。
 三つ目は(図A(a))と同じように,XML文書をDOM(Document ObjectModel)ツリーと呼ばれるオブジェクトに展開してRDBに格納するタイプです。データ構造を解析した状態で保存しているため,高パフォーマンスが期待できます。XMLデータベースとRDBのメリットを併せ持つタイプと言えます。

      正規化なしにRDBはあり得ない RDBに格納するデータの項目が多くなってくると,一つのテーブルで管理するのは大変です。例えば,図1のテーブルでは,部署名に変更があったときに複数個所を修正しなければなりませんでした。そこで,図2のようにテーブルを二つに分割することで,修正すべき個所をまとめましたね。
 この例からわかるようにRDBでは,テーブルを分割することによって管理を容易にできます。しかし,やみくもに分割すればよいわけではありません。下手に分割するとかえって扱いにくくなってしまいます。
 ここで紹介する「正規化(Normalization)」と呼ぶ手続きに従ってテーブルを分割していくと,管理しやすいRDBを自然に作ることができます。正規化は実用的なRDBを作成するための必須知識ですから,しっかりとマスターしておきましょう。正規化は,第一正規形から第五正規形までがあります。また,正規化される前の表を非正規形と呼びます。通常は,第三正規形まで正規化されることが多く,第四正規形,第五正規形までの正規化はあまり行われません。
ステップ1:繰り返しをなくして第一正規形に 図3はある問屋の受注伝票です。ここに記述してあるデータをRDBに格納することを考えましょう。
図3●今からRDB化する受注伝票
 図3に登場するデータ項目を整理して表形式で表したものが図4の非正規形の表です。図4は図3の内容をほぼそのまま表形式にしただけです。一つの伝票番号に複数の行が含まれていますから,テーブルとは呼べませんね。
図4●図3の受注伝票に現れるデータを表形式でまとめたもの [画像のクリックで拡大表示]
 そこで,一つの伝票番号に現れる複数の行(「繰り返し部分」と呼びます)をなくして,RDBのテーブルにすることを考えます。一番簡単なのは,繰り返し部分のすべての行に,伝票番号から得意先住所までの列を追加することです(図5)。しかし,これでは見るからにムダが多いですね。伝票番号が決まれば受注年月日~得意先住所はただ一つに決まります。そこで,伝票番号ごとに受注年月日~得意先住所を格納するテーブル(受注テーブル)を作成します。そして,繰り返し部分の各行に対しては,伝票番号だけを付加したテーブル(受注明細テーブル)を作成します。こうして作成した二つのテーブルを,第一正規形と呼びます(図6)。
図5●図4を一つの行に複数の行が含まれないように修正した表。見るからにムダが多い [画像のクリックで拡大表示]

図6●第一正規形のテーブル
ステップ2:主キーに注目して第二正規形に 第一正規形になったテーブルを第二正規形にするには,主キー以外のすべての列の値が主キーによってのみ決まるように,テーブルを分割します。主キーの値によってほかの列の値が一意に決まることを,「主キー以外の列が主キーに従属する」と言います*1
 第一正規形である図6の受注テーブルは,主キーである伝票番号が決まれば,受注年月日,得意先コード,得意先名,得意先住所が一意に決まります。したがってこのテーブルはすでに第二正規形を満たしていることになります。
 図6の受注明細テーブルはどうでしょう。伝票番号と商品コードの組み合わせを主キーとすれば,残りの列が従属しているように思えます。しかしテーブルの内容についてよく考えると,商品名と単価は,商品コードだけに従属していることがわかります。そこで,商品コードを主キーにするテーブル(商品テーブル)を新たに作成して,図6の受注明細テーブルの商品名と単価の列を商品テーブルに切り出します。
 こうして作成した第二正規形のテーブルが図7です。小計は単価×数量によって求められるため,テーブルで保持する必要がないと考えて削除しました。
図7●第二正規形のテーブル
ステップ3:主キー以外の列の関係を調べて第三正規形に 第三正規形ではさらに,主キー以外の列の間に従属関係がないようにテーブルを分割します。図7の第二正規形の受注テーブルを見てください。伝票番号が決まると確かに得意先名と得意先住所が決まります。しかしよく考えてみると,得意先名と得意先住所は,得意先コードを決めただけで決まってしまいます。すなわち得意先名と得意先住所は,得意先コードに従属しているわけです*2
 そこで,得意先コードを主キーとするテーブル(得意先テーブル)を作成し,そこに元々の受注テーブルの得意先名と得意先住所の列を切り出します。
 こうして作成した第三正規形が図8です。最初は一つだったテーブルが,四つに分割されてしまいましたね。正しく分割できたかどうかは,第三正規形のテーブルから,元のデータ,つまり受注伝票を再現できるかどうかで確かめることができます。試してみましょう。
図8●第三正規形のテーブル [画像のクリックで拡大表示]
 受注テーブルから受注番号と受注年月日と得意先コードが得られます。得意先コードからはさらに,得意先テーブルを用いて得意先名と住所がわかります。受注の内容については,伝票番号から受注明細テーブルを用いて,商品コードと数量がわかります。そして商品コードからは,商品テーブルに基づいて,商品名と単価がわかる,といった具合です。これで受注伝票を再現できました。もし再現できなければ,正規化の過程で何かミスをしていることになります。
正規化のメリット 正規化のメリットの一つは,データに変更があったときにどこを直せばよいのかが一目瞭然になるということです。例えばある得意先の名前や住所が変わったとしましょう。非正規化表では受注テーブルのデータのすべての得意先名や住所を変える必要があります。しかし第三正規形では得意先は得意先テーブルだけで管理していますから,1行変更するだけで済みます。こうしたテーブルを「マスター・テーブル」と呼びます。
 注文に後から別の商品を追加したい場合には,受注明細テーブルに同じ伝票番号の行を追加するだけで済みます。ほかに,得意先ごとの割引率を設定したり,注文の日付によって違う単価を適用するなどの複雑な機能をアプリケーションに実装しやすくなります。
 受注明細テーブルの商品コードに参照整合性制約を設定することで,商品テーブルにある商品以外は注文できないようにすることも可能です。すなわち,正規化することによって,データベースの整合性を保ちやすくなると同時に,不正なデータや間違ったデータの入力を防止できるのです。
 もっとも,検索時のパフォーマンスが問題になるときに,あえて正規化を崩すこともあります*3。非正規化の方法としては,関連付けられた複数のテーブルをまとめる,繰り返し部分を保持させる,合計金額など計算で求められる値をあらかじめ保持しておく,などがあります。ただしこうした非正規化は第三正規形まで正規化した後に行うものであり,正規化をしなくてもよい,ということでは決してありません。
 最近では,ハードウエアの性能向上により,非正規化を行う機会はかなり少なくなっています。きちんと正規化を実施したうえで,性能検証やデータベースのチューニングをして,それでもどうしてもパフォーマンスが不足する場合にだけ非正規化を検討するようにしてください。繰り返しになりますが,RDBのテーブルと言えば正規化と肝に銘じておいてください。まずは正しく正規化することが最優先です。
知っておきたいキーワード
O/Rマッピング
 テーブルに正規化を施すことのメリットの一つとして,SQL文の記述が容易になるということがあります。しかし,いくら正しく正規化されていたとしても,オブジェクト指向で開発したアプリケーションからリレーショナル・データ・モデルのRDBを扱う際には必然的に「インピーダンス・ミスマッチ」(後述)が発生してしまいます。これはオブジェクト指向とリレーショナル・データ・モデルの設計思想の違いに起因するもので,結果としてわずらわしい処理が付きまとうことになります。
 この問題を解決するアプローチとして期待されているのが「O/Rマッピング」です。O/Rマッピングは,アプリケーション側のオブジェクトと,RDB側のテーブルを対応付ける(マッピングする)ことです。
オブジェクトとテーブルを対応付ける インピーダンス・ミスマッチについて簡単に説明しておきましょう。オブジェクト指向アプリケーションでは,SQL文を使ってデータベースからデータを取得した際に,そのままオブジェクトに格納できないため,オブジェクトとして組み立て直す必要があります。更新の際には,オブジェクトからデータを取り出して,SQL文を作成するコードを書かねばなりません。
 これは,プログラムのコードにSQLが入り込んでしまうことにもなります。そのため,データベースの構造に変更があった際に,検索結果を取得している部分や,オブジェクトから値を取り出してSQL文を組み立てる部分にまで影響が及んでしまい,プログラムの保守性を悪くする要因になります。
 O/Rマッピングをサポートするフレームワークを使うと,検索結果をオブジェクトに組み立て直す処理や,オブジェクトからデータを取り出してSQL文を作成する処理を,開発者が直接書く必要がなくなります。その結果,アプリケーションとデータベースの結びつきが弱まり,開発生産性や保守性が向上することを期待できます。
 O/Rマッピングのフレームワークを使用したアプリケーションからRDBへのアクセスの手順は図Bのようになります。開発者が用意するのは,オブジェクト,データ・アクセス・オブジェクト,マッピング・ファイル,テーブル,の四つです。これらのうち,O/Rマッピングのフレームワークに固有なマッピング・ファイルと,アプリケーション側でデータの受け渡しに利用するデータ・アクセス・オブジェクトについて説明しましょう。
図B●O/Rマッピング・フレームワークを使ってアプリケーションからRDBにアクセスする仕組み [画像のクリックで拡大表示]
 マッピング・ファイルには,オブジェクトのデータをマッピングする先,データ型をはじめとする文字数,操作の制限などを記述します。データ・アクセス・オブジェクトには,アプリケーションから受け取ったオブジェクトを使ってO/Rマッピングのフレームワークに処理を依頼したり,O/Rマッピング・フレームワークから受け取った処理結果のオブジェクトをアプリケーションに渡す処理を記述します。O/Rマッピング・フレームワークの多くは,データベースの構造定義に基づいて,マッピング・ファイルの作成を支援する機能を備えています。
 マッピング・ファイルとデータ・アクセス・オブジェクトが用意できたら,アプリケーションのオブジェクトからデータベースのテーブルにアクセスするのは簡単です。データ・アクセス・オブジェクトの検索や更新を行うメソッドの引数に,値やオブジェクトを入れてアクセスするだけです。検索であれば,このメソッドの戻り値として検索結果のオブジェクトが返されます。
 現在入手できるO/Rマッピング・フレームワークにはそれぞれ特徴があり,使い勝手も千差万別です。実際の開発で使用する前に,一度試してみることをお勧めします。また,O/Rマッピング・フレームワークも内部ではSQLを使っています。デバッグやチューニング時にはSQLをメンテナンスする必要がありますから,SQLを理解しなくてもよいというわけではありません。

SQLを制するものはRDBを制する 現在では,「データベースといえばRDB」ですが,ここにまで普及したのは「SQL」があったから,と言っても良いでしょう。SQLは元々「Structured Query Language」の略で,日本語では「構造化問い合わせ言語」と呼ばれます*4。RDB内のデータの検索や更新のほか,テーブルの作成や削除,データベースを管理・操作するのに利用します。
 RDBが出始めた1970年代前半は,階層型データベースやネットワーク型データベースが全盛でした。これらのデータベースではデータベースとアプリケーションのプログラムが密接に関係しているため,システムの保守や変更が難しいという問題がありました。RDBはSQLを介してデータベースにアクセスするという方法を採ることで,こうした問題点の解決を図ったのです。当初は,SQLの処理の負荷が大きいためにパフォーマンスを出しにくいという問題がありました*5。しかし,1990年代半ばくらいのマシン性能の飛躍的な向上に伴い,RDBが普及するようになったのです。
 SQLはANSIの規格にのっとっており,様々なRDBMSで互換性が保たれているため,ベンダーに依存せず利用できます*6。しかし,多くのRDBソフトはSQLの規格から外れた独自の命令も用意していますから注意が必要です。システムを開発するときには,データベースを変更する際のリスクを考えて,できるだけベンダー非依存のSQLを使用しましょう。
基本になる命令は四つだけ SQLで利用できる命令は大きく,(1)データ操作言語(DML:Data ManipulationLanguage),(2)データ定義言語(DDL:Data Definition Language),(3)データ制御言語(DCL:DataControl Language)に分類できます(表1)。
表1●SQLの命令の種類
 DMLはテーブルに対して,データの検索,追加,変更,削除などの操作を行います。DDLはテーブルやインデックス*7を作成・変更・削除したり,RDBのユーザーを作成・変更・削除するのに使います。DCLは,データベースを制御するための命令です。ユーザーに権限を与えたり,権限を剥奪したり,アクセスを制御できます。処理の確定(コミット)や取消(ロールバック)など,トランザクション処理*8を制御する命令も,DCLに含まれます。
 ここでは最も頻繁に利用されるDMLについて説明しましょう。DMLに分類される命令のうち,ぜひとも覚える必要があるのは四つ。行を検索するSELECT,行を追加するINSERT,行を更新するUPDATE,行を削除するDELETEです。順に説明しましょう。
 SELECTの構文構造は次の通りです。SELECT文の最後には必ずセミコロン(;)が必要です。これはどんなSQL文にも言えることであり,これがないとエラーになりますから注意してください。
 SELECT 列名 FROM テーブル名 WHERE 検索条件;
SELECT句には検索したい列名を,FROM句には検索したいテーブル名を,WHERE句には検索条件を記述します。すなわち,「テーブル名」のテーブルから「検索条件」に合致した行の「列名」を表示する,といった具合です。図2のようなテーブルがあった場合,テーブル名として社員テーブルを,検索条件として男を,列名として氏名を指定して検索すると,男性社員の氏名がずらりと表示されます。WHERE句の検索条件の書き方については,後で詳しく説明します。
 列名の代わりにアスタリスク(*)を指定することもできます。この場合,検索条件に合致した行のすべての列を検索結果として返します。WHERE句以降を省略すると,検索条件なしと解釈され,テーブルに含まれるすべての行について,列名で指定した列の値を表示します。
 これら二つを組み合わせるとどうでしょう。すなわち,列名にアスタリスク(*)を指定してWHERE句以降を省略すると,テーブルの内容をすべて表示するSQL文になります。
 INSERT文はテーブルに行を一つ追加するための命令で,構文構造は次の通りです。
 INSERT INTO テーブル名(列1,列2,列3) VALUES(値1,値2,値3);
INSERT INTO句でデータを追加するテーブルと列を指定し,VALUES句で追加する値を記述します*9。列名と値はペアになっており,追加した行の「列1に値1」「列2に値2」「列3に値3」がそれぞれ入ります。列と値の数が違ったり,列のデータ型*10と違うデータを入れようとするとエラーになります。値を指定しなかった列にはテーブルに設定されているデフォルト値が入ります。
 テーブル名の後に続く列を省略することもできます。その場合はテーブルのすべての列が追加対象となり,VALUES句に列の数ぶんの値を,定義されている順に記述します。
 UPDATEは行のデータを更新するのに利用します。構文は次の通りです。
 UPDATE テーブル名 SET 列名 = 値 WHERE 条件;
UPDATE句でテーブル名を指定し,SET句で更新したい列名と値,WHERE句で更新したい行を指定する条件を記述します。SET句にカンマ区切りで複数の列を記述することで,複数の列を更新できます。
 SELECT文と同様にWHERE句を省略すると全件が更新対象とみなされます。更新を行う際は同じ条件で検索を行い,更新したい行が検索結果として抽出されているかをあらかじめ確認するとよいでしょう。
 行の削除を行うDELETEは次のように利用します。
 DELETE FROM テーブル名 WHERE 条件;
FROM句でテーブル名,WHERE句で削除の対象となる行を指定する条件を記述します。SELECT文やUPDATE文と同様に,WHERE句を省略すると条件なしと見なされ,すべての行が削除されてしまいますから注意が必要です。
SQL自由自在 以下ではSQLを使った様々な命令(問い合わせ)について紹介します。同じ結果を返すSQL文は一つとは限りません。できるだけ,わかりやすく,効率のよい方法を選ぶようにしましょう。
WHERE句を駆使すれば様々な検索が可能 まず,SELECT文,UPDATE文,DELETE文で条件を指定するWHERE句の使い方について紹介しましょう。WHERE句を使いこなせば,様々な条件を指定してデータを検索できます。以下ではSELECT文を例としてあげますが,DELETE文やUPDATE文に対しても同じように使えます。
●比較演算
 <,<=,=,>=,>などの比較演算子を使って具体的に条件を指定します。次の例は,商品テーブルから商品コードが0001の商品名を返すSQL文です。
 SELECT 商品名 FROM 商品テーブル WHERE 商品コード = '0001';
不等号を使えば,単価が100円以下の商品名を返すといったSQL文も作れます。
●論理演算
 比較演算子と,論理積(AND),論理和(OR),否定(NOT)といった論理演算子を組み合わせて使うことで,より凝った検索ができます。例えば,単価が100円以上,かつ,1000円以下のすべての商品名を返すには
 SELECT 商品名 FROM 商品テーブル WHERE 単価 >= 100 AND 単価 <= 1000;
のようにSQL文を記述します。
●あいまい検索
 WHERE句でLIKEを使うことで,ワイルドカード*11を用いた検索が可能になります。例えば,マイクロソフトのSQL Serverや日本オラクルのOracleでは,「%」は任意の文字複数個,「_」は任意の1文字を表します。したがって,次のように書くと商品名の最後に「ナイフ」と付くすべての商品名が返されます。
 SELECT 商品名 FROM 商品テーブル WHERE 商品名 LIKE '%ナイフ';
 INを使うと複数の値を指定して,いずれかに該当するものを検索できます。例えば単価が100,200,300,400,500のいずれかである商品名を検索するには次のようにSQL文を記述します。
 SELECT 商品名 FROM 商品テーブル WHERE 単価 IN(100,200,300,400,500);
 BETWEENを使うと,データの範囲を指定できます。次の例は,単価が100円以上,1000円以下の商品名が返されます。
 SELECT 商品名 FROM 商品テーブル WHERE 単価 BETWEEN 100 AND 1000;
BETWEEN条件は,比較演算子と論理演算を組み合わせて表すこともできます。
SELECTの検索結果を加工して返す SELECT文では,検索結果の列の値をそのまま返す以外に,加工して返すこともできます。
●重複を取り除く
 SELECT文でDISTINCT句を使って列名を指定すると,検索結果から重複を取り除いて返します。先に説明したように
 SELECT 顧客名 FROM 受注テーブル;
は,受注テーブル内の顧客名をすべて返しますが,受注テーブルに同一顧客名の行が複数あると,その数だけ同じ顧客名が返されます。そこで SELECT DISTINCT 顧客名 FROM 受注テーブル;
のようにSQL文を記述すれば,顧客名の重複を取り除きますから,それぞれの顧客名は一度しか返しません。 ●検索結果に対して演算を行う
 SELECTでの検索結果に対して指定した演算を施した値を返すように指定することもできます。例えば,商品テーブルから,すべての商品名と単価を検索し,単価と並べて消費税込み単価も表示させたいときは,次のように記述します。
 SELECT 商品名,単価,単価*1.05 AS 税込金額 FROM 商品テーブル;
ここでは,AS句を使って単価に1.05をかけた値に対して,税込金額という別名を付けています。こうすると,単価に1.05をかけた値の列が税込金額と言う列名で表示されます。
●昇順/降順で並べ替える
 検索結果が数値の場合,小さい順(昇順),あるいは大きい順(降順)に並べ替えて返してくれると便利なことが多いですね。ORDER BY句を使えば,検索結果を特定の列について並べ替えることができます。次のSQL文は,社員テーブルのすべての社員の社員番号と氏名を,社員番号が小さい順に並べ替えて返します。
 SELECT 社員番号,氏名 FROM 社員テーブル ORDER BY 社員番号;
ORDER BY句には列名に続けて,昇順(ASC),降順(DESC)を指定できます。次のSQL文は社員番号の大きい順に社員番号と氏名を返します。
 SELECT 社員番号,氏名 FROM 社員テーブル ORDER BY 社員番号 DESC;
昇順を指定するときにはORDER BY句にASCを指定しますが,何も書かなければ昇順を指定したものとされます。
●合計値や平均値を求める
 SQLは,合計や平均を求める関数を用意しています。これらの関数はまとめて,集合関数と呼ばれます。例えば商品テーブルに含まれている商品の単価を合計するには,SUM関数の引数として列名(単価)を指定して次のように書きます。
 SELECT SUM(単価) FROM 商品テーブル;
単価の平均を求める場合は,AVG関数を使います。
 SELECT AVG(単価) FROM 商品テーブル;
COUNT関数を使えば,検索結果得られた行数を数える(カウントする)ことができます。例えば次のSQL文は,商品テーブルの商品数を返します。
 SELECT COUNT(*) FROM 商品テーブル;
 集合関数にはほかに,最大値を求めるMAX関数,最小値を求めるMIN関数などがあります。
●列の値でグループ化して扱う
 GROUP BY句を使うことにより,特定の列の値についてグループ化して扱うことができます。次の例は,社員テーブルの部署名をグループ化して,部署ごとに社員数をカウントして返します。
 SELECT 部署名,COUNT(*) FROM 社員テーブル GROUP BY 部署名;
 HAVING句を用いると,グループ化した行に対して,条件を指定して絞り込みを行うことができます。
 SELECT 部署名,AVG(年齢) FROM 社員テーブル GROUP BY 部署名 HAVING AVG(年齢) <= 35;
この例では部署ごとの平均年齢を求めて,平均年齢が35歳以下の部署の部署名と平均年齢を表示します。
●問い合わせ結果を使って問い合わせる
 SELECT文の中に,別のSELECT文を埋め込み,埋め込んだSELECT文の結果を用いて次の検索を行うことができます。これを「副問い合わせ」と呼びます。
 SELECT 商品名 FROM 商品テーブル WHERE 単価 > (SELECT AVG(単価) FROM 商品テーブル);
カッコ内のSQL文では,商品テーブルの全商品の単価の平均値を求めています。そしてWHERE句で,単価がその平均値より大きいという条件を指定して,商品テーブルから該当する商品名を取り出します。副問い合わせはいくらでも入れ子にできますが,パフォーマンスの面からはできるだけ少なくしておくのが良いでしょう。
知っておきたいキーワード
SQL/XMLとXQuery
 現在,XMLデータを扱うためのSQL/XMLとXQueryという規格が注目を集めています。SQL/XMLはANSIが2003年に規格化したSQL 2003に含まれるSQLの拡張機能です。一方,XQueryはW3C(World Wide Web Consortium)で標準化作業が行われている規格です。どちらもXMLデータを扱うための規格なのですが,両者を比較すると,SQL/XMLは“SQL寄り”で,XQueryは“XML寄り”と言えるでしょう。
SQL/XMLはSQLの拡張 SQL/XMLを使うと,RDBのデータの検索結果をXML形式に変換して取得できます。通常のSQL文と同様にして使えますから,SQL文を使用した経験がある人にとってなじみやすい仕組みと言えます。
 最も基本となるSQL/XML文を紹介しましょう。図Cはテーブルからデータを取り出して,XML形式に変換するSQL/XMLの例です。XMLELEMENTはXML要素を作成するために,XMLATTRIBUTESはXML属性を作成するために使用します。図を見ると,XMLELEMENTを指定したところが要素になっていて,XMLATTRIBUTESを指定したところが属性になっていることがわかるでしょう。
図C●ITEMSテーブル(左)からデータを取り出してXML形式に変換するSQL/XMLの例 [画像のクリックで拡大表示]
 市販の主要RDBのうち,SQL 2003そのものの採用を明示している製品はOracleなど少数の製品に限られます。しかし,そのサブセットであるSQL/XMLについては,多くの製品が現行版あるいは次版で同等機能を実現することをうたっています。
XQueryはXMLを意識する必要がある XQueryで問い合わせに利用できる句には,for,let,where,order by,returnがあります。それぞれの頭文字をとってFLWOR表現式と呼びます。
 それぞれの機能は,SQLのSELECT文の句に対応付けられます。SQLのSELECT句に当たるのがreturn,FROM句に当たるのがforとlet。そして,WHERE句に当たるのがwhere,ORDER BY句にあたるのが,order byです。
 XQuery1.0の仕様はXPath2.0を含んでいます。XPathは,XML文書が表すツリー構造の場所を特定するための言語です。XQueryではXPathを使用してXMLの各要素を指定します。
 図DはXQueryでの問い合わせの例です。左のXML文書に対して,XQueryを適用して右のXML文書を作成しました。priceタグの値が100以上のbookを抜き出して,title,priceと一緒に返しています。変数($b)や繰り返し(for)などのFLWOR表現式と,XPath($b/title,$b/priceなど)を用いてXMLのデータ構造を表現しています。通常のSQL文と同様に,四則演算,比較演算,論理演算を行うこともできますし,複数のXML文書に対してXQueryを実行することもできます。
図D●条件に合致するデータを取り出すXQueryの例 [画像のクリックで拡大表示]
 XQueryと並行して,XQJ(XQuery forJava)という規格も開発中です。JDBCがSQLを用いるRDBへのJava用APIであるのと同様に,XQJはXQueryを用いるXML文書へのJava用APIです。Java技術の標準化団体Java Community ProcessがJSR-225として策定中です。

[ 本帖最后由 bgx5810 于 2007-5-28 10:30 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-28 10:29:13 | 显示全部楼层
SQLこれだけ知っていれば 大丈夫!


ステップ1 RDBとSQLの濃い関係を知る SQL*1は,ISO(国際標準化機構)とANSI(米国規格協会)によって標準化が進められている,RDB*2を操作するためのプログラミング言語です。では,RDBがどのようなデータベースかわかりますか? それを知るために,まずは次のコードをご覧ください。
SELECT 氏名,住所 FROM 名簿 WHERE 従業員NO = 8421237 これはSQL文(ステートメント),つまりSQLのコマンドを使用したコードです。初めてSQLを見る方は,ちょっと戸惑うかもしれませんね。SQLを使うと,たったこれだけのコードで,従業員NOが8421237の人の氏名と住所をデータベースから取り出すことができるのです。BASICやC言語とはかなり雰囲気が違い,すごく単純に見えますよね。でも単純だからこそ,このSQLからRDBについて基本的なことがたくさん読み取ることができます。
まずはじめに「テーブル」ありき 前述のSQLコードをもう一度,よく見てください。SELECT,FROM,WHEREという三つの英単語が何らかのキーワードになっていることは察しが付きますSQL*3。SELECTを使ったSQL文の詳細は後述しますが,ここではまずFROMの後ろに注目してください。「名簿」という文字から,「ああ,これは名簿という名前のデータベースかな」ということはすぐに思われるかもしれません。
 しかし,残念ながら「名簿」はデータベースの名前ではありません。SQLでは,FROMの後ろに「テーブル名」を記述するのです。テーブルとは「表」のことです。テーブルは,タテの列(カラムまたはフィールド)と,ヨコの行(ローまたはレコード)から構成され,行と列が交差した部分にデータ(値)を格納します(図1)。
図1●テーブルのイメージ
 RDBは,この「テーブル」が基本中の基本です。RDBにデータを登録する,というのは実はテーブルにデータを登録する,ということなのです。「あれ,じゃあ,データベースとテーブルとは違うの?」。その通り。テーブルとデータベースは違います。
テーブルが複数集まったものを総称してデータベースと呼ぶのです。 なお,これら「テーブル」や「行」「列」などの言い方はデータベース製品やベンダーによって異なりますが,この記事では,テーブル,レコード,フィールドと呼ぶことで統一します*4
テーブルには「主キー」と「構造」がある 図1の「名簿」テーブルを見ていると,「なんだ,これならExcelなどの表計算ソフトで使うスプレッドシートと同じじゃないか」と思われるかもしれません。スプレッドシートも2次元の行列が基本ですから,確かによく似ています。しかし,スプレッドシートとテーブルが明らかに異なる点があります。テーブルには「主キー」と「構造」があるのです。それも一度「主キー」や「構造」を定義したら,その定義通りのデータしか格納できません。どういうことでしょうか。図1のテーブルの「従業員NO」に注目してください。
 従業員NOは「主キー(PrimaryKey)」と呼ばれる特別な意味を持ったフィールドです。主キーとは,レコードを1行ずつ識別するためのフィールドで,主キーとなったフィールドには,違うレコードで同じ値を登録することは許されません。図1の例では,同じ従業員NOを持つレコードは,テーブルの中に存在できないのです。 冒頭で紹介したSQLコードを実行すると,従業員NOが8421237の人(ここでは菊田恵子さん)の氏名と住所を取り出すことができます。このとき,
WHERE 従業員NO = 8421237
とあったのは,この主キーの特性を利用しています。つまり従業員NOが8421237の人は一人しかいないので,一人分のレコードが取り出せるわけです。このようにRDBでは,テーブルに主キーを設定するのが大原則です*5。主キーは一つのフィールドでも,あるいは複数のフィールドを組み合わせても構いません。例えば,従業員NOと氏名をセットにして主キーとすることもできます。
 一方,もう一つのキーワード「構造」とはなんでしょうか。表1は,図1の「名簿」テーブルの構造を示したものです。SELECTの後ろにあった「氏名」「住所」,また,WHEREの後ろにあった「従業員NO」が,実は「フィールド名」を意味していることは,もうわかりますね。フィールド名というのは,各フィールド(列)の見出しの名前なわけです。
表1●図1の「名簿」テーブルの構造
 また,各フィールドに対し,文字列を格納するのか,数値を格納するのか,といったデータ型をあらかじめ設定するのもRDBのルールです。スプレッドシートでは,一つの列に文字だろうが数値だろうが自由に格納できますが,RDBでは定義したデータ型の値しか格納できません。例えば数値型のフィールドに「東京都」という文字列を格納しようとするとエラーになります。
 表1には「NULL値の扱い」で,NOT NULLという指定が設定されています。NULL(ナルまたはヌルと読む)とはデータが空っぽの状態のことで*6,NOTNULLの指定があるフィールドには,必ず値を入れなければならない,という意味になります。したがって,名簿テーブルに新たにデータを登録するときは,必ず従業員NOと氏名を入力しなければなりません。これもスプレッドシートとは違う点です。
テーブルは関連付けられる RDBのだいご味は,テーブル同士を関連付けて,複雑なデータを表現できる点にあります。
 図2は顧客名簿,製品,購入履歴という三つのテーブルを関連付けている様子を表現しています。このようにテーブル同士を関連付けることを「リレーションシップをとる」と呼びます。ここで注目してほしいのは,購入履歴テーブルに,顧客名簿テーブルの主キーである「名前ID」フィールドと,製品テーブルの主キーである「製品コード」フィールドが存在する点です。これにより,購入履歴テーブルから,顧客の名前を参照したり製品名を参照することが容易にできるようになるのです。このような,ほかのテーブルからコピーされた主キーを「外部キー(Foreign Key)」と呼びます。
図2●リレーションシップのイメージ。三つのテーブルを関連付けている
[画像のクリックで拡大表示]
SQLの役割は,RDBを定義し操作すること さて,RDBの概念が少しは理解できてきたところで,RDBとSQLの関係を整理しておきましょう。
 表1で紹介したテーブルの構造をデータベースに登録するには,実はSQLを使います。また,定義したテーブルに,データを挿入したり削除したり検索したりするのにもSQLを使います(図3)。SQLが定義できるのはテーブルだけではありません。図2で紹介したリレーションシップも,参照整合性制約*7というルールに基づいてSQLを使って定義します。インデックス*8,ビュー*9など,データベースの中にあるオブジェクト,さらにデータベース全体の定義にもSQLを使います。
図3●SQLの二つの役割(SQL文はAccess+MSDE利用時)
[画像のクリックで拡大表示]
 つまり,RDBの構造を決めるにもデータを操作するにも,すべてSQLを使うのです。SQLがRDBにとってどれくらい重要なものなのか,これで理解できますよね。このようなSQLの二つの顔を,DDL(データ定義言語:Data DefinitionLanguage),DML(データ操作言語:Data Manipulate Language)と分類して呼ぶこともあります*10
SQLにはホスト言語が必要だ では,「それほど重要なSQLなら,データベース・アプリケーションを開発するのに,SQLさえ知っていればいいのでは」と思ってしまいがちですが,残念ながらSQLだけでアプリケーションは作れません。SQLはあくまでデータベース処理言語。それ以上のことはできないのです。VisualBasic(VB)のようにウィンドウを作ったり,フォームを作成してボタンを配置したり,コンポーネント部品を利用する,といったことはできません。データベースの扱いが専門です。
 実際にデータベース・アプリケーションを開発するには,VBやC/C+のような「ホスト言語の中からSQLを利用する」必要があります*11
 では,SQLとBASICやC/C++,Java,COBOLのようなプログラミング言語とは何が違うのでしょうか。一般に,BASICなどは「手続き型言語」,SQLは「宣言型(非手続き型)言語」と呼びます。前者は,何か処理を行うのに,その処理手順をいちいち記述しなければならない言語。一方,後者は「○○がほしい」とヒトコトで済む言語です。
 ラーメン店に行ったときのことを想像してみましょう。ネギラーメンを注文するなら,「ネギラーメンください!」で済みますよね。これがSQLです。これに対し手続き型言語でネギラーメンを注文するなら,「まず麺を茹でて,その間にネギを油で炒めて。そしてスープを…」と,すべての段取りを細かく指示しなければなりません。つまり,手続きです。SQLのような宣言型言語のほうがわかりやすく,初心者にも簡単なことは明らかでしょう。データベースと言えばRDBのことだというくらいRDBが普及し,アプリケーション開発でSQLが使われるのは,このようなSQLの簡単さから来ている,と言ってもいいかもしれません。
 以降は,SQLのデータ操作で使われる四つのコマンド,SELECT,INSERT,UPDATE,DELETEについて,具体的にその使い方を見ていきましょう。
SQLを実行しているのはだれ?
 SQLがデータベース処理用のプログラミング言語であるならば,いったい何がSQLのコードを実行しているのでしょうか。
 答えはRDBです。AccessやOracle,SQLSererなど最近の主要なRDBのほとんどは,SQLコードを受け取ってそれを解釈し,実行するエンジンを備えているのです。したがってアプリケーションがSQLを発行すると,RDBはそのSQLコードの中身をチェックし,どのように処理するか計画を立てて実行するわけです。アプリケーションは,その実行結果やエラー通知などを受け取るだけです。クライアント(アプリケーション)とサーバー(RDB)とが,「SQLによる依頼」と「その結果」をやり取りしている,と考えればいいでしょう。
 ただしSQLは製品によってその実装の仕方が異なります。ISOやANSIで標準化が進んでいますが,現実には製品によって微妙な差(方言)があります。特に製品によって独自に拡張/追加した関数があるので,例えばOracleで利用できたSQLがそのままSQLServerで使えるとは限らないのです。とはいっても,SELECT,INSERT,UPDATE,DELETEといったSQLの主要なコマンドの記述方法は同じです。基本さえマスターすれば,仮に異なるデータベースを扱うことになってもコードの記述にそれほど迷うことはないでしょう。

AccessのDBエンジン
 SQLの勉強をするには,実際にSQLを操作してみるのが一番です。できれば,マイクロソフトのデータベース・ソフトAccessなどを使って,SQLを使用する手順を体験してみるのがいいでしょう。
 ちなみにAccessは,Access2000からデータベースのエンジンが選択できるようになりました。それまでのデータベース・エンジンはJet,もう一つのエンジンがSQLServerと互換性のあるMSDE( Microsoft Data Engine) です。JetとMSDEではSQLを処理する仕組みが異なります。また,操作方法も異なります。現在は,SQL Serverの技術をベースにしたMSDE2000(Microsoft SQL Server2000 Desktop Engine)が,Webサイトで無償提供されています。
ステップ2 何はともあれSELECTは覚えよう SQLの基本はSELECTです。SQLは「SELECTに始まり,SELECTに終わる」と言っても過言ではありません。SQLなんて面倒だ,なんて思っている方でも,最低SELECTの使い方だけ覚えていれば,いずれは必ず役に立つときが来るはずです。
フィールド名とテーブル名を指定する SELECTは,テーブルの中から値を取り出すコマンドです。取り出すと言っても,どのような条件で取り出すかを指定しなければなりません。基本的な構文は図4の通りで,フィールド名,テーブル名,そして取り出す条件を指定します。これはステップ1でも紹介したので,なんとなくわかりますよね*12
 なお,SELECTに限らずSQL文を記述するとき,各コマンドを続けて書こうが,途中で改行しようが関係ないことも覚えておくといいでしょう。
図4●SELECTコマンドの構文と例文
SELECT Name,Address FROM table1
を,
SELECT
Name,Address
FROM table1

としても,動作します。
 ただし気を付けなければならないのは,大文字小文字の扱いです。基本的にSQLは大文字小文字を区別しません。SELECTをselectと書いても実行します。しかしデータベース・ソフトのOracleでは,テーブル名やインデックス名などについて厳密に大文字小文字を区別します。EMPとempでは別のテーブルの名前として処理するのです。このことは後でパフォーマンスにも大きな影響を与えてきます。
 フィールド名を複数並べるときは「,」(カンマ)で区切ります。ただし列挙する最後のフィールド名の後ろにカンマは付けません。また,フィールド名の順番は,テーブル構造の順番とは無関係で,図4の例文で言えば,「名前」フィールドの前に「住所」フィールドを書いても問題ありません。
フィールド名を変更したり並べ替えたりできる すべてのフィールドを取り出したいとき,いちいちフィールド名を列挙するのは面倒ですよね。その場合に使うのが,*(アスタリスク)です(図5)。*(アスタリスク)を使えば,テーブルに定義されているすべてのフィールドを取り出せます。ただし,フィールドの数が多いと当然,処理の負荷は高くなりパフォーマンス(実行スピード)は低下します。開発時にフィールド名をいちいち記述するのは面倒なので*(アスタリスク)を使うことはよくありますが,実際に運用する段階になったら,本当に必要な最小限のフィールド名だけにするようにしてください。
図5●*(アスタリスク)を使ったSELECT文を実行したところ
 また,値を取り出したとき,フィールド名を変更したい場合もあります。図5の例で言えばフィールド名はNameとAddressですが,これではわかりにくいと思う方もいるでしょう。そこでSELECTでは,フィールド名にエイリアス(別名)を付けることができます。エイリアスは,フィールド名を列挙するときに,
[フィールド名] AS [エイリアス名]
のように,ASに続けて定義します。図6は,この方法を使って,N a m eフィールドを「氏名」,Addressフィールドを「住所」として表示した例です。
図6●フィールド名にエイリアスを使い,ORDER BY句で並べ替えるSELECT文を実行したところ。Addressフィールドの値はすべて漢字なので,シフトJISコードの順番で並べ替えられている
 なお図6 では,ORDER BYも使っています。ORDER BYは,取り出した値の順番を特定のフィールドの値に基づいて並べ替える命令で,SQL文の最後に,
ORDER BY [フィールド名]
と記述します。デフォルトは昇順(A~Z,0~9,あ~ん)ですが,ORDER BY Address DESCのようにキーワードDESCを付加すれば降順(Z~A,9~0,ん~あ)に並べ替えて値を表示します*13
検索用のキーワードや関数を使いこなす SELECTには,さまざまなキーワードや関数が用意されています。例えば図7のテーブル「ScoreList」を見てください。このテーブルは,個人別の成績(Score)を記録したテーブルです。フィールドとしてID,Name(名前),Class(クラス),Score(得点)が定義されています。フィールドClassには,3-4や3-5など,同じ値が繰り返し登録されていますね*14。このテーブルから,ScoreListに登録されているClass名だけ抽出したい場合,単純にSELECTするだけでは,3-4や3-5という値が複数列挙されてしまいます。
図7●SELECT文のサンプル。
(a)DISTINCTを使うと値の重複を防いで表示する。(b)GROUP BYを使うと,指定したフィールド別に集計処理を行う。AVG ( )は平均値を求める関数
[画像のクリックで拡大表示]
 そこで,フィールド名の前にDISTINCTキーワードを付加すると,重複した値を「まとめて」表示することができます(図7の(a))。
 ScoreListのテーブルで,「各Class別の平均Scoreを算出したい」場合はどうすればいいでしょう?このようなとき,SQLに標準で用意された関数を使います。例えば,数値型のフィールドの平均値を求めるには,AVG関数を使います。しかし,AVG関数だけでは,「各Class別の」という部分を処理できません。そこで使うのがGROUPBYです。SELECT文の終わりに,
GROUP BY [フィールド名]とすれば,そのフィールドでグルーピングした集計結果を表示します(図7の(b))*15
 SQLには,AVG関数のほかさまざまな関数が標準で用意されています(表2)。
表2●SQLでよく使われる関数
AccessやOracle,SQL Serverなどデータベースによって,使える関数が微妙に異なるので注意してほしい
[画像のクリックで拡大表示]
また,簡単な計算を行うための算術演算子もあります(表3)。算術演算子や変換関数,文字列関数は頻繁に使うので,よく覚えておくといいでしょう。
表3●SQLで使える算術演算子と使い方
WHERE句を自由自在に操ろう SELECTを使いこなす最大のポイントは,WHEREにあります。ここでどのような抽出(検索)条件を設定するかで,取り出せる値が変わってくるからです。
 図8は,WHEREに比較演算子の>=を用いて,得点が80点以上のレコードを抽出した例です。
図8●WHEREを使ったSELECT文
[画像のクリックで拡大表示]
このようにWHEREでは,
[フィールド名] 演算子[値]
と記述するのが基本です。WHEREで使用できる演算子としては,表3で紹介した算術演算子のほか,表4の比較演算子や論理演算子が挙げられます。また,文字列を結合させる連結演算子もあります。ただし連結演算子は,データベースがOracleの場合は||ですが,SQL Serverでは+など異なっていますので,SQLを書くときは注意してください。
表4●WHERE句で使える演算子
[画像のクリックで拡大表示]
 演算子の中でも特に役に立つのがLIKEです。LIKEは文字列のパターン・マッチングに使うもので,ワイルドカードとして%を使います*16図9は,テーブルEMPの中で,「大」の字で始まるNameのレコードを取り出す,LIKEを使ったSELECT文の例です。
図9●LIKE演算子を使ってパターン・マッチングしているSELECT文。
Access+MSDEで実行した
[画像のクリックで拡大表示]
 さて,実際にSQLを使ってみるとすぐに迷ってしまうのが,引用符の問題でしょう。SQLの中で文字列を扱う場合は’(シングルクォート)を使います。例えば,テーブルEMPの中からフィールドNameの値が「鈴木」のレコードを検索したい場合は,
SELECT * FROM EMP WHERE Name = '鈴木'
とします。’(シングルクォート)を文字として扱いたい場合は,’’と続けて記述します。
検索結果を使って検索するサブクエリー RDBでさまざまなテーブルを扱っていると,あるテーブルをSELECTした結果を使って,ほかのテーブルをSELECTしたい,というニーズが出てきます。これを実現するのがサブクエリー(副問い合わせ)です。
 サブクエリーは図10のように,WHEREの後ろの条件部分に,カッコでくくった別のSELECT文を記述することで表現します。ただし注意しなければならないことが一つ。サブクエリーによって取得する値は,必ず1レコードでなければなりません。図10の例で言えば,Deptフィールドが「製造」のレコードは1件しかありませんが,もしサブクエリーの中の検索条件を変更して,
SELECT Name,Address FROM table1 WHERE Name = ( SELECT Name FROM table2 WHERE Dept = '研究開発' )
としたら,このSQLコードはエラーとなります。というのもtable2を見ればわかるように,Deptが研究開発であるレコードは2件あるからです。
図10●サブクエリー(副問い合わせ)の構文と例文
 「これでは実用的でない!」と思われるかもしれませんが,回避する方法もあります。WHEREから後ろの条件で,=(イコール)ではなく,表4の演算子で紹介したINを使うのです。つまり,
SELECT Name,Address FROM table1 WHERE Name IN ( SELECT Name FROM table2 WHERE Dept = '研究開発' )
とすれば,このSQL文は正しく実行できるようになります*17
ステップ3 データ編集ができれば一人前 ステップ2で学んだSELECTは,データベースのデータを何らかの形で取り出すことが目的のコマンドでした。しかしあくまでデータを取り出すだけ。テーブルの中身を変更することはできません。そこで次に,レコードを挿入するINPUT,値を変更するUPDATE,レコードを削除するDELETE,の三つを見てみましょう*18。フィールドの数と値の数を一致させるのがINSERTのポイント テーブルにデータを挿入するのがINSERTコマンドです。データを挿入したいテーブル名,フィールド名,値をINSERT句の中に記述します(図11)。
図11●INSERT文の構文と例文
ポイントは,
●指定するフィールド名と値の数が一致していること
●挿入する値のデータ型が,フィールドのデータ型と合致していること

の2点です。これが満たされないとINSERTはエラーになります。
 また,実際にINSERTを使うときにつまずくのは,文字列の扱いでしょう。SELECTの場合は,データ型が文字列でも数値でもそのまま取り出せましたが,INSERTでは,’SQL’のように,文字列を’(シングルクォート)でくくる必要があります。数値はそのままで大丈夫です。では日付型のデータの場合はどうか。これはSELECTの時と同じです。日付型の関数を使って年月日を取り出したり,日付そのものならOracleやSQLServerでは,文字列と同じように’(シングルクォート)でくくります。
 なお,ほかのテーブルから抽出したデータを挿入したい場合として, INSERT INTO~SELECT句があります。例えば
INSERT INTO table1(Name,Address)
SELECT Name,Address FROM table2

とすれば,table2から抽出した複数のレコードを一度にtable1に挿入できます*19
 このほか,INSERTで注意しなければならないこととして,NULL値の扱いがあります。NULLは,ステップ1でも少し述べたようにデータが空っぽの状態のことで,半角や全角のスペースとは異なります。テーブル定義の中に,NOTNULL(NULL値は認めない)の設定があるフィールドに対しては,空白のデータを挿入できません。これは後述するUPDATEの場合も同じです。データベースを設計するときに,NOT NULLをどう設定するかは,実際の運用でかなり重要になってくるので十分注意してください。
今あるデータを変更するUPDATE UPDATEは,テーブルにあらかじめ登録してあるデータを変更する場合に用いるコマンドです(図12)。テーブル名と変更したいフィールド名,変更したい値,そして「どのレコードを変更するか」という情報をWHEREで記述します。気を付けなければならないことは,WHEREを付けないと,すべてのレコードを変更してしまう点です。基本的に,UPDATEとWHEREは同時に使うものだとセットで覚えておいたほうがいいでしょう。
図12●UPDATE文の構文と例文
 更新する値として,ほかのテーブルからの検索結果を使いたい場合は,サブクエリーを使うこともできます。例えば次のコードは,table2から取り出したフィールドNameの値をtable1のNameに格納するSQL文です。
UPDATE table1 SET Name = (SELECT Name FROM table2 WHERE ID= 3) WHERE NID = 321
このとき,サブクエリーの値が一つだけ,という点に注意してください。table2から取り出した値(この場合ならIDが3のNameの値)が複数あるとエラーになります。
 また,複数のフィールドを同時に更新したい場合は,
UPDATE [テーブル名]
SET [フィールド名]=[値],
[フィールド名]=[値],

WHERE [条件]

のようにカンマで区切って記述します。
テーブルからレコードを削除するDELETE DELETEは,テーブルの中にあるレコード(行)を削除するコマンドです(図13)。レコードの中の各フィールドではなく,行そのものを削除します。
図13●DELETE文の構文と例文
 注意すべきなのは,U P D A T E と同様,WHERE句を指定しなければ,すべてのレコードを削除する点でしょう。ただしDELETEを使うと,記録を残したりインデックスを作成し直すなどの処理が発生するので*20,すべてのレコードを削除したければ,DELETEではなく,
TRUNCATE TABLE [テーブル名]
を使うべきです。TRUNCATETABLEなら,DELETEのような処理は行わず,高速にすべてのレコードを削除できます。ただしTRUNCATETABLEは,SQLの標準規格にあるコマンドではありません。Accessでは使えませんが,OracleやSQL Serverでは利用できます。
 DELETEと区別してほしいのがDROPTABLEコマンドです。DELETEはテーブルの中のレコードを削除するもので,仮にすべてのレコードを削除したとしても,テーブルそのものは残っています。ですから,またあとからレコードを追加することができます。一方,DROP TABLEは,
DROP TABLE [テーブル名]
として使い,テーブル定義そのものを削除します。データベース全体のメンテナンスを行う場合はDROP TABLE,テーブルのメンテナンスの場合はDELETE(またはTRUNCATE)と覚えておきましょう。
ステップ4 複数テーブルをSQLで操作する データベースはテーブルの集合です。現実に使われるデータベースを考えてみても,顧客テーブル,受注テーブル,手配テーブルなど複数のテーブルが一つのデータベースの中に格納できます。RDBのだいご味は,これら複数のテーブルをいかに結合させてデータを取り出すか,とも言えます。
結合にもいろいろある 結合は,二つのテーブルを組み合わせるのが基本です。と一口に言っても,さまざまな方法があり,
1)交差結合(クロス結合)
2)内部結合(等結合)
3)外部結合(左外部,右外部)
4)和結合

の四タイプに分かれます。1)の交差結合とは,二つのテーブルのレコードをすべて並べるだけですので,現実のアプリケーション開発で使うことはないでしょう。2)の内部結合は,二つのテーブルで共通するフィールドを結合させて,共通するデータを列挙させる方法です。3)の外部結合は,共通するデータと共通しないデータを同時に列挙させる方法で,二つのテーブルのどちらのレコードを優先させるかで,左外部結合と右外部結合とに分かれます。最後の4)和結合は,二つのSELECTをつなげるものです。
同じ値同士を結びつける内部結合 内部結合は,最もよく使われる結合の方法です(図14)。FROMの部分に,結合するテーブルの名前をカンマで区切って記述し,WHEREに,結合させるフィールド名を=(イコール)で結びます。図にもあるように,INNERJOIN ~ ONという構文もあります。
図14●内部結合の構文と例文
 ここで注意しなくてはならないのが,フィールド名の記述方法です。二つのテーブルを一つのSQLの中で取り扱うので,両方のテーブルに同じ名前のフィールドがあった場合,それがどちらのテーブルのものなのか,明確にする必要があるのです。そこで,
[テーブル名].[フィールド名]
のように.(ドット)でテーブル名とフィールド名をつなげるのがルールです。
 内部結合では,二つのテーブルで値が一致しているもの“だけ”が取り出されます。図14の例で言えば,table1のName「田中」はtable2にありません。したがって,結合した結果の中には田中さんのデータは表示されないのです。
外部結合と和結合 外部結合は内部結合と違って,二つのテーブルで値が一致していないレコードも表示します。ただし,二つのテーブルを左右に並べたとき,どちらを優先にするかで,左外部結合と右外部結合とに分かれます。
 左外部結合では,LEFT JOIN~ONという構文を使います(図15)。図15の例文を見ればわかるように,左外部結合では,図14の内部結合では表示できなかったtable1の「田中」のレコードが表示されるようになります。一方,右外部結合は,RIGHT JOIN~ON構文を使います。
図15●左外部結合の構文と例文
 外部結合は,左右どちらのテーブルを基準とするかで,得られる結果が変わってしまいます。実際のアプリケーション開発では,得られる結果がどうなるかを事前によく調べてから使うことをお勧めします。
 最後に紹介する和結合は,内部/外部結合とはやや異なり,二つのSELECT文をUNIONキーワードで接続して使います(図16)。注意する点は,二つのSELECT文で,フィールドの数が一致していること。一方が二つで一方が三つではエラーとなります。図16の例では,table1とtable2どちらかに存在しているNameの値が得られていることがわかるでしょう。重複する値がある場合は,1行にまとめてくれるのが便利なところです。
図16●和結合の構文と例文。
例文は「table1とtable2どちらかのテーブルに存在しているNameを抽出しろ」の意味になる
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-28 10:36:03 | 显示全部楼层
DB初心者はここから読もう!!
ExcelユーザーのためのAccess超入門

石橋君(以下,石)●博士,ご無沙汰です!
博士(以下,博)■おお,石橋君,久しぶりじゃな。
石●はい,休み中は忙しくて。
博■ほぅ,何をしておったんじゃ?
石●ちょいと副業を。
博■副業?
石●はい!で,博士にお願いが。
博■なんじゃ,またか。君はいつも困った時にしか顔を出さん。
石●いや~,照れるじゃないですか。
博■誰もほめてはおらんがな。まぁよい。君と漫才をやっておる暇はない。で,どうしたんじゃ?
石●はい,Microsoft Excelで名簿を作ったのですが,会員が増えて条件に合う相手を探すのが大変になりまして。そこで,ちょちょいとマクロを作っていただこうかと参上し次第です。
博■会員? 相手? 何の名簿じゃ?
石●はい,お見合い紹介所の会員名簿です。
博■石橋君,まさかいかがわしいサイトとかかわってはおるまいな?石●博士,もっと僕を信用してくださいよ。ネット上のサイトじゃありません。個人でやってるお見合い相手の紹介所です。
博■ん~,石橋君だから信用できんのじゃが…よかろう。いろんな条件を設定して,それに合う会員をピックアップすればよいのじゃな?
石●さすが,博士呑み込みが早い。博士,マクロが完成したら,とびっきりの美人を紹しますよ。
博■石橋君,そういうことは先に言いたまえ。では,スペシャルなヤツを作ってやろう。
石●まったく,調子がいいんだから。
博■何をもごもご言っておるんじゃ? 作業にとりかかるぞ。
 お見合い相手?のデータが増えすぎてExcelでうまく検索できなくなった石橋君は,Excelのことなら何でも知ってる博士のところに相談に来ました。Excelユーザーの方なら,データが増えすぎて条件検索がうまくできなくなったという経験は1度ぐらいあるのではないでしょうか。自分で解決できない石橋君は,博士をうまくおだてて便利なマクロを作ってもらおうという魂胆のようです。
 すっかりのせられた博士は,すぐに作業に取りかかるのですが,どうやらExcelではなく違うソフトを使うみたいです。石橋君の目論見はうまく成功するのでしょうか?
大量データの検索にはデータベースが便利博■ところで石橋君,Microsoft Accessを知っているかね?*1
石●はい,データベースのソフトですよね?
博■そうじゃ,ではデータベースとは何じゃ?
石●え~っと…データの共有化,統合管理,高い独立性を目的として,データをあらかめ定義した形で集中的に集積し管理する仕組み*2です。
博■そうじゃな。で,平たく言うとどういうことじゃ?
石●そこまでは書いていません。
博■…。まぁよい。理屈よりも実践じゃ!
石●博士? 気分が乗ってるところ申し訳ないのですが,僕はExcelのマクロを作ってほしいんですけど。
博■それじゃ! Excelのユーザーは,何でもかんでもExcelでやろうとする。確かにExcelは多機能で,簡易データベースとしても利用できるが,基本的には表計算のソフト。石橋君も,このあたりでAccessを勉強してみてはどうかな?
石●なるほど。で,Accessって便利なんですか?
博■石橋君にしては鋭い質問じゃな。例えば,石橋君が希望する条件検索のマクロをExcelで作るためには,当然VBAの知識とテクニックを必要とする。しかし,Accessにはクエリと呼ぶ機能があって,簡単な式を作るだけで複雑な条件抽出ができるんじゃ。
石●おぉ,それは素晴らしい。それなら僕でもできそうだ。博士,他にはないんですか?
博■そうじゃな,データベースにはたいていリレーションシップ*3というものがある。例えば,PCの部品を管理するデータベースを作成したとしよう。それには,部品のスペック表と仕入先の一覧表がある。二つの表に「仕入先ID」という共通のフィールドを作って,それをリレーションシップで関連付ける。すると,二つの表が互いにリンクされて,仕入先にどの部品を発注しているのかといったことが一目でわかるのじゃ(図1)。
図1●部品表と仕入先一覧という二つの表がある(a),仕入先IDという共通のフィールドを使って二つの表にリレーションシップを設定する(b),すると仕入先一覧で部品表を同時に参照できる(c)
[画像のクリックで拡大表示]
石●それは,一つの表にできないんですか?
博■もちろん,できるさ。しかし,考えてもみたまえ。部品の細かいスペック項目の横ずらずらと仕入先の住所や電話番号,担当の名前が並ぶのはスマートじゃない。それに,ハードディスクとCD-ROMドライブはA社,きょう体と電源はB社といった具合に,同じ仕入先から異なる部品を仕入れている場合は,表に同じメーカーの情報を何度も入力することになる。
石●なるほど,部品表と仕入先一覧という二つの表にすれば,部品表に入力するのは仕入先IDだけで済むんですね。
博■そういうことじゃ! 複雑なデータは複数に分けて関連付ける,つまりリレーションシップを構築すれば,すっきりするということじゃ。
石●なかなか便利そうですね? ところでフィールドって何ですか?
博■それはExcelでも使う用語なんじゃがな。ここで説明するより,データベースを作りながら説明した方がいいじゃろ。
石●わかりました。じゃあ,早速データベースとやらを作りましょう。
 ということで,石橋君と博士はAccessを使ってデータベースを作成することにしました。すでにAccessをお持ちの方は,ぜひ一緒にAccessを動かしながら読み続けてください。お持ちでない方は,誌面を見ながら,Accessの操作方法やExcelとの違いを感じてください。
空のデータベースを作ることから始まる博■では,石橋君,Accessを起動したまえ。
石●ラジャー! Accessを起動しました(図2)。あれ? 博士,何もありませんよ。どこにデータを入力するんですか?
図2●Microsoft Accessを起動したところ [画像のクリックで拡大表示]
博■そうなんじゃ,Excelは起動すると同時に,まっさらなワークシートが表示されて,入力可能な状態になる。Microsoft Wordもそうじゃな。しかし,Accessの場合はそうはいかん。
石●そうか! じゃあ[新規作成]ボタンをクリックすれば…あれ? やっぱり何も出てきません。
博■おしい,Accessの[新規作成]ボタンは[作業ウィンドウ]を表示するためのボタンなんじゃ。Accessをショートカットから起動した場合は,作業ウィンドウはすでに右側に表示されておるから,画面上なにも変化がない。
石●なんだか奇妙なソフトですね。
博■そうじゃな,Excelのユーザーには違和感があるかもしれん。まぁ,すべては慣れじゃよ。慣れてしまえばどうということはない。石橋君,右側にある[空のデータベース]リンクをクリックしたまえ。
石●はい。あれあれ? 何も作ってないのに,ファイルを保存するダイアログボックスが出てきましたよ。
博■うむ,適当な名前で構わん。保存したまえ。
石●はい,保存しました。おや,何かウィンドウが出てきましたね(図3)。
図3●データベース・オブジェクトを管理/操作するデータベース・ウィンドウ
博■うむ,これでデータベースを設計する準備ができた。それはデータベース・ウィンドウと言って,データベース・オブジェクトを操作するためのウィンドウじゃ。
石●データベース・オブジェクト?
博■Accessには,データベース・ウィンドウ左側のアイコンで選択できる七つのオブジェクトがある。それぞれについては,データベースを作成しながら,おいおい説明しよう。
 Accessのデータベース・オブジェクトについて簡単に説明しましょう。Accessを利用するには,最初に「データベース」を作成します。データベースは一般に“入れ物”とか“器”にたとえられます。最初の時点では何も中身が入っていない入れ物です(Accessでは,データベースを作成すると,拡張子.mdbを持つファイルが作成されます)。この入れ物(データベース)の中に作っていくのがデータベース・オブジェクトです。
 データベース・オブジェクトで一番重要なのは「テーブル」です。テーブルは,簡単に言えばデータの一覧表であり,データベースの実質的な本体です。その他のオブジェクトはテーブルを操作する,またはテーブルをベースに作られるオブジェクトです(カコミ記事「その他のデータベース・オブジェクト」参照)。
 一般にデータベースを作成するには,まずテーブルの設計から始めます。Accessの場合,テーブルの設計とは,テーブルにフィールドを作成して,そのプロパティ(属性)を設定する作業です。フィールドは,表の項目(列)に相当するものです。フィールドにプロパティを設定しておくといろいろと便利なのです。例えば,Excelで作る表は,列名に関係なく自由にデータを入力できますが,Accessのフィールドでは,プロパティを設定することで入力できるデータを制限できます。数値型に設定したフィールドには数値しか入力できません。文字を入れようとすると,エラー・メッセージが出て入力できません。これをExcelで実現しようとすると,セルのChangeイベントを検知して,入力したデータをチェックするといったマクロが必要です。
 このようにテーブルの設計は,フィールドごとに入力可能な値やサイズを設定して,テーブルを使いやすくする大事な作業です。そのためには,テーブルの各フィールドにどんなデータを入力するのかを事前に決めておく必要があります。とはいえ,Accessではフィールドのプロパティを後で変更することもできますので,最初は気軽にやってみましょう。
その他のデータベース・オブジェクト
 本文ではテーブル,フォーム,クエリの三つのオブジェクトを紹介しましたが,Accessには他にレポート,マクロ,モジュール,ページの四つのデータベース・オブジェクトがあります。ここで簡単に説明しておきます。
●レポート
 レポートは,テーブルの内容やクエリによる抽出結果を,体裁を整えて印刷するためのオブジェクトです。日本語で「帳票」などと呼ばれるものに近いです。レポートは,フォームと同様にウィザードで容易に作成できます。基となるテーブルやクエリを選び,印刷したいフィールドを選択して[完了]ボタンをクリックすれば出来上がりです。その後,デザイン・ビューでレイアウトなどの見栄えを自由に変更できます。
●マクロ
 Accessで言うマクロは独自のもので,VBAで作成するマクロとはまったく別物です。Accessに慣れていないVBAユーザーは,必ずと言っていいほど混乱するので注意が必要です。
 Accessのマクロは,一連の操作手順(アクション)を登録して,それに名前を付けたものです。マクロを新規作成するには,オブジェクトの[マクロ]を選んで,[新規作成]ボタンをクリックします。表のようなデザイン・ウィンドウで,実行したいアクションを並べていきます。アクションは選択方式なので,割と簡単に作成できます。VBAの知識は必要ありません。
 一つだけ,マクロのテクニックを紹介しましょう。データベースを開くと同時に,マクロを実行したい場合にどうするか?アクションで選択できる項目に,そうしたものがあればわかりやすいのですが,残念ながらありません。答えは,マクロの名前を「AutoExec」にすることです(図A)。MS-DOSユーザーには,懐かしくてなじみ深い名前ですよね。MS-DOSの起動時に実行されるバッチ・ファイル「AUTOEXEC.BAT」と同じネーミングです。
 図Aのマクロは,データベースを開くと同時に,[名簿編集]フォームを開き最大化するものです*A。このマクロを実行したくない場合は,Shiftキーを押しながらデータベースを開きます。
図A●データベースを開くと同時に起動するAutoExecマクロを作成する様子
[画像のクリックで拡大表示]
●モジュール
 VBAでプログラムを作成,実行するためのオブジェクトです(図B)。Accessには,独自のマクロ機能があるので,それを補う形で利用することが多いかもしれません。また,ほかのアプリケーション,例えばExcelからデータベースを操作する場合に,抽出クエリなどをモジュール内のプロシジャに登録しておけば,それを外部から実行することが可能です。
 モジュールの構造は,ExcelなどのOfficeアプリケーションと同じですが,オブジェクト,プロパティ,メソッドは当然のことながらAccess独自のものです。特にテーブルは,外見がExcelのワークシートに似ているので,セルの感覚で操作できそうな錯覚を覚えますが,まったく違うオブジェクト・モデルなので注意してください。
 なお,Excel,Wordのようなマクロ記録機能(操作手順を記録してVBAプログラムを自動生成する機能)はありません。このため,プログラムを作成するために必要なオブジェクト名,メソッド,プロパティなどを知るには,マニュアル,ヘルプ,サンプル・プログラムなどを読むしかないのです。多少の手間は覚悟した方がいいでしょう。
図B●VBAのプログラムを作成するAccessのVisual Basic Editor
[画像のクリックで拡大表示]
●ページ
 AccessのテーブルやクエリをHTMLファイルとして保存するオブジェクトです。Excelでは,メニュー[ ファイル]-[Webページとして保存]で,ワークシートの内容をHTML形式で保存できます。Accessの場合は,メニュー[ファイル]-[エクスポート]で表示されるダイアログボックスで[ファイルの種類]に[HTMLドキュメント]を選んで保存する方法と,オブジェクトの[ページ]を選択して,デザイン・ビューやウィザードを利用してHTML形式のファイルを作成する2種類の方法があります。
 ウィザードを使って,本文で使った会員抽出クエリをHTMLファイルにしてみました。条件にヒットした会員の情報を,カード形式で一人ずつ表示します(図C)。画面下部のボタンを使って表示するレコードを選択できます。ただし,HTMLのソースにはパソコン上の絶対パスが含まれますので,Webサーバーにアップする際には相対パスに変更するか,サーバー上の絶対パスに変更しましょう。
図C●会員抽出クエリを基に作成した「ページ」(HTMLページ)
[画像のクリックで拡大表示]

いろいろなデータ型を選択できる博■石橋君,[デザインビューでテーブルを作成する]をダブルクリックしたまえ*4
石●ラジャー!博士,表のようなものが出てきましたよ。
博■うむ,一番左の列にフィールドの名前を入力し,その右の列で入力するデータの型を選択する。その右の「説明」はコメントを入力する列じゃ。フィールドに入力するデータの意味を書いておくと後々役に立つことになる。まず[フィールド名]に「氏名」と入力したまえ。
石●はい,入力しました。おや,[データ型]のところに[テキスト型]と表示されて,[▼]ボタンが出てきました。
博■そうじゃな,[テキスト型]はデフォルトじゃ。人の名前はテキストじゃから,そのままで構わん。数値などを入力するフィールドの場合は,[▼]ボタンをクリックして,適切な型を選ぶんじゃ(図4)。
図4●テーブルのデザイン・ビューで「データ型」を選択している様子
[画像のクリックで拡大表示]
石●いろんな型がありますねぇ。
博■データ型の詳細は,F1キーを押してHELPを表示すれば,そこに書いてある。わかりにくそうなものだけ説明しておこう。「オートナンバー型」は,各レコード*5に連続する番号(シリアル・ナンバー)を振りたいときに利用する。このデータ型を指定したフィールドには,ユーザーが入力をしなくても,自動的にユニーク(一意)な数値が入力される。また,デザイン・ビューを閉じる際に,「主キー」が設定されていないと,設定を促すメッセージ・ボックスが表示され,設定に同意するとオートナンバー型の「ID」フィールドが自動的に作成される。
石●それは,重要なんですか?
博■データベースのレコードには,各レコードを識別できるユニークな値やコードが必要なんじゃ。これがないと混乱する場合がある。特に理由がない限り設定しておくべきじゃな。
石●なるほど。
 一般に,RDB(リレーショナル・データベース)と呼ばれるデータベース・ソフトでは,主キーは非常に大事です。Excelの表ではまったく同じ内容のレコード(重複レコード)を入力できますが,RDBのテーブルでは重複レコードは許されません。必ずほかのレコードと区別できる(レコードを一意に識別できる)フィールドが必要です。これを主キー*6と言います。主キーは,上のようにオートナンバー型のフィールドを使うのでもいいですし,複数のフィールドを組み合わせて指定することもできます。
データ入力が選択式になるルックアップ博■データ型では「ルックアップウィザード」というのも選択できるが,これは正確にはデータ型ではない。データ入力を楽にするための機能と言えよう。さっきPCの部品を管理するデータベースの例を出したとき,部品表には仕入先IDを入力すると説明したじゃろ?その場合,仕入先IDを入力するフィールドを「ルックアップウィザード」にして,ウィザードに従って仕入先一覧のテーブルとリレーションシップを設定しておくのじゃ。そうすると,データを入力する際に,そこで入力可能な項目を選択できるようになる(図5)。項目を選択すればいいので,入力の手間が省けるし,タイプ・ミスなどを防ぐメリットもある。
図5●ルックアップウィザードを指定したフィールドでは,リレーションシップが設定されたほかのテーブルの値を選択できる [画像のクリックで拡大表示]
石●おぉ,それは便利ですね。
博■そうじゃろ。その他のデータ型は,VBAに精通している石橋君なら,説明はいらんじゃろ。
石●も,もちろんですよ。…後でHELPを読んでおこう。
博■何か言ったか?
石●いえ,なんでもありません。
博■では石橋君,お見合い紹介所の名簿に必要なほかのフィールドを自分で作成したまえ。
石●はい!出来ました(図6)。
図6●石橋君が作成した会員名簿を登録するテーブルのデザイン
[画像のクリックで拡大表示]
博■おぉ,早いじゃないか,石橋君。
石●はい,誌面の都合で。
博■そんな料理番組みたいなことは言わんでよろしい。どれどれ? ん~,やはりExcelユーザーの特徴が出ておるな。
石●え,どこにですか?
博■それは[年齢]の欄じゃ。この年齢は,どこかで計算して入力するのかね?
石●何を言ってるんですか,博士。関数と計算式を使って生年月日から自動計算するに決まってるじゃないですか。毎日,生年月日をチェックして書き換えるなんてできませんよ。
博■そうじゃろ。Excelならば,そうなんじゃ。しかしな,Accessではそうもいかんのじゃ,石橋君。
石●もしかして,計算式を入力できないとか?
博■鋭い!石橋君。そうなんじゃよ。テーブルは,Excelのセルのように計算式を入力することはできんのじゃ。
石●絶対にですか?
博■そう言われると自信がなくなるが,わしの知り得る限りはできんな。Accessの場合はクエリで計算をするのが一般的じゃな。
石●それを抽出条件に使用できるんですか?
博■もちろん,できる。クエリを作るときに説明しよう。
石●なるほどね。便利なのか,面倒なのか…。
博■考え方次第じゃよ。ところで,石橋君。一つ便利な機能を教えてあげよう。[氏名]フィールドをクリックして,プロパティの[ふりがな]のところをクリックする。次に右端に現れた[…]ボタンをクリックするんじゃ。
石●メッセージ・ボックスが保存を要求しています。
博■構わん,保存したまえ。
石●は!ウィンドウが表示されました(図7)。
図7●名前などのふりがなを自動的に入力するためのふりがなウィザード
博■うむ,ふりがなを入力するフィールドと文字の種類を選択したら[完了]ボタンをクリックするんじゃ。
石●博士,またメッセージ・ボックスが。変更の確認を要求しています。
博■構わん,[OK]ボタンをクリックしなさい。
石●はい,元の画面に戻りました。何がどうなったのでしょうか?
博■ふふふ,いずれわかるよ石橋君。さあ,データの入力じゃ。右上の[×]ボタンでテーブルのデザイン・ウィンドウを閉じて,テーブルをダブルクリックして開きなさい。
石●ラジャー! Excelのワークシートのようなまっさらな表が現れました。ここにデータを入力すればいいのですね。最初に僕の名前を入力してみます。お?おぉ!博士,名前を入力したら,ふりがなが自動で入力されました。
博■そうじゃ,さっきの設定はそれだったんじゃよ。しかもじゃな,Excelと違って他のアプリケーションからコピーしてきた名前でもちゃんとふりがなが出てくる。
石●それは素晴らしい。どうしてなんですか?
博■それは…マイクロソフトに聞きたまえ。
石●知らないんですね?
博■ノーコメント。そんなことより速やかにほかのデータを入力しなさい。わしは,ミーちゃんとお茶をしてくる。
石●ラジャー!
 博士は,さっさとお茶に行ってしまいました。一人残された石橋君は,データ入力の作業を続けています。しかし,単調なデータ入力作業に飽きた彼は,いろいろとAccessの機能をいじったり,HELPを読んだりし始めました。そして,Accessでは通常,データの表示や入力をするために,「フォーム」を使うのだということに気付きました。
 データベースのテーブルは,実際のデータを格納する表です。それ自体は,Excelの表に似ています。しかし,Excelのようにユーザーがテーブルを直接操作することはあまりありません。通常,データベースを使ったアプリケーションでは,ユーザーがデータを閲覧したり入力したりするためのユーザー・インタフェースを作り,それをユーザーに使ってもらうようにします。Accessでは,そのユーザー・インタフェースをフォームと言います。これは,先ほど少し触れましたデータベース・オブジェクトの一つです。Excel/VBAでも,ユーザー向けに独自に作成した操作画面をフォームと言いますから,フォームの使い道はおおよそわかるという方が多いでしょう。
 フォームのレイアウトや表示する項目などは自由に決められます。つまり,ユーザーごとに異なるものを作成できるのです。例えば,あるユーザーには,会員名簿テーブルのうちの名前,性別,生年月日だけを表示するフォームを,別のユーザーには自分の住所と電話番号を変更できるフォームを用意するといった使い分けができるのです。複数のテーブル間にリレーションシップが設定されている場合は,それらのテーブルから必要な項目を選択して,あたかも一つのテーブルであるかのように見せるといったことも可能です(図8)。
図8●リレーションシップが設定された二つのテーブル「仕入先一覧」「部品表」を一つのフォームで表示した例
 Accessでは,簡単にフォームを作成できます。「ウィザードを使用してフォームを作成する」をクリックするとフォーム・ウィザードが起動します(図9)。そこで基となるテーブルと必要なフィールドを選択し,フォームの形式(1レコードずつ表示するか一覧形式で表示するか,など)やスタイル(背景色など)を選ぶと,ひな型となるフォームを自動的に生成します。
図9●フォームを簡単に作成できるフォーム・ウィザード
 石橋君も自分で図10のようなフォームを作って,再びデータ入力作業に取りかかりました。こうして作成したフォームは,後で自由に変更できます(図11)。しばらくして,お茶を終えた博士が戻ってきました。
図10●石橋君が作成したフォーム [画像のクリックで拡大表示]

図11●作成したフォームは,デザイン・ビューで容易に編集できる
[画像のクリックで拡大表示]
Excelデータを取り込む機能がある博■どうかね,石橋君。入力ははかどっているかね?
石●はい,博士。3分の1くらいはデータ入力しました。
博■なんじゃ。まだその程度か。
石●そんなこと言ったって,大変なんですよ。やっぱりExcelのマクロにすればよかったな。それだったら,博士に作ってもらうだけだから簡単だし…。
博■しょうがないなぁ。そろそろ教えてやるかな。
石●博士,マクロを作ってくれるんですか?
博■いや,そうじゃない。実は,AccessはExcelで作ったデータを取り込むことができるんじゃよ。
石●なんですって!博士~,そういうことはもっと早く教えてくださいよ。
博■まあまあ,何事も修行じゃ。最初から楽をしては何も身に付かんからな。では,今までの作業はとりあえず置いておいて,新たに空のデータベースを作成しなさい。ブツブツ言わない。こうして簡単にデータベースを作ったり消したりできるのがAccessの良いところじゃよ。メニューバーの[ファイル]-[外部データの取り込み]-[インポート]をクリックすると,Excelファイルを選択するダイアログボックスが表示されるじゃろ。ファイルを選択したら[インポート]ボタンをクリックするんじゃ。
石●はい,[ワークシートインポートウィザード]が表示されました。
博■うむ,[次へ]ボタンをクリックしたまえ。
石●はい,画面が変わりました(図12)。
図12●Excelデータを取り込むためのワークシート・インポート・ウィザード
博■うむ,[先頭行をフィールド名として使う]チェックボックスがあるじゃろ?Excelでも表を作成する場合は,行の先頭に項目名を付けていることが多い。その場合,[先頭行をフィールド名として使う]にチェックを入れると,行の先頭をフィールド名としてインポートしてくれる。よく覚えておくように。では,[完了]ボタンをクリック。
石●はい。メッセージ・ボックスが表示されました。
博■[OK]ボタンをクリック。
石●はい。博士,テーブルが一つ出来ました。
博■開いてみたまえ。
石●おぉ,エクセレント! Excelの表がAccessのテーブルになってます。
博■うむ。ただし,データ型はAccessがデータから判断して自動的に設定している。実際のデータと合ってない場合もあるから,その点を注意してチェックするように。
石●博士,大丈夫です。問題ありません。
博■そうか。では,問題解決じゃな。
 Excelにデータを持っているという方は,このインポート・ウィザードを使ってAccessのテーブルを作るのが一番手早いでしょう。ただし,博士が言ったように,データ型の設定には注意する必要があります。また,リレーションシップなどデータベースの特徴的機能を活用するためには,重複レコードがないかどうかをチェックしたり,場合によっては正規化と呼ぶルールに基づいて表を分割するなどの作業が必要になります。
条件抽出はクエリで石●博士,何か忘れていませんか? 僕は条件に合う会員を抽出したいんです。
博■おぉ,そうじゃったな。なに,簡単なことじゃ。クエリを作成すれば,条件抽出は簡単にできる。
石●そう,それ,クエリですよ,僕が欲しいのは。
博■テーブルを閉じて,[ウィザードを使用してクエリを作成する]をダブルクリックしたまえ。
石●はい,[選択クエリウィザード]が表示されました。
博■すべてのフィールドを[選択したフィールド]に移動して[完了]ボタンをクリックじゃ。
石●はい。博士,テーブルみたいなものが表示されました。でも,全部のレコードがあるみたいですけど?
博■石橋君,あわてちゃいかん。まだ,抽出する条件を設定しとらんからな。それを閉じて[デザイン]ボタンをクリックするんじゃ。
石●はい。なにやら表のようなものが出てきました。
博■そうじゃな,そこで抽出する条件を設定するんじゃ。試しに女性だけを抽出してみよう。テーブルの性別の欄には,どう入力してあるのかね?
石●女性は♀,男性は♂です。
博■うむ,では[性別]の[抽出条件]のところに「"♀ "」と入力(図13(a))。そして,[×]ボタンをクリックして,メッセージ・ボックスが表示されたら[はい]ボタンをクリックじゃ。
図13●(a)クエリの「性別」フィールドに抽出条件を設定したところ。(b)抽出条件をクエリ実行時に入力するように,パラメータ・クエリに変更 [画像のクリックで拡大表示]
石●はい。ウィンドウを閉じました。
博■では,クエリをダブルクリックするんじゃ。
石●はい。博士,出ました。おぉ,出来てます。ちゃんと女性だけが表示されてます。
博■当然じゃよ,石橋君。わしは博士だぞ。
石●なんだか意味不明ですが,博士はすごいです。
博■うむ,ではもう一つ便利な機能を教えてあげよう。今作成したクエリでは,女性しか抽出できない。男性を抽出したい場合はどうする?
石●はい,クエリの抽出条件を変更します。
博■それも間違いではない。しかし,抽出条件を変更していちいち保存するのは大変だとは思わんか?
石●そうですね。面倒ですね。
博■そうじゃろ。では,クエリ実行時に条件を指定できたらどうじゃ?
石●それは便利ですね。そんなことができるんですか?
博■まかせなさい。今作ったクエリのデザイン・ウィンドウを開いて,[性別]の[抽出条件]のところに「[性別を入力]」と入力するんじゃ(図13(b))。半角の[ ]ではさむところが味噌じゃからな。間違えないように。
石●はい,入力しました。
博■よろしい,ではデザイン・ウィンドウを閉じて,再度クエリを開いてみたまえ。
石●博士,メッセージ・ボックスが表示されました。
博■そこに♀か♂を入力するんじゃ(図14)。
図14●パラメータ入力画面
石●はい,では試しに♂を。おぉ,出ました。男性だけが表示されています。
博■これをパラメータ・クエリと言う。覚えておくのじゃぞ。後は,今までの応用じゃ。自分で頑張って作りたまえ。
クエリには計算式も指定できる石●博士,ちょっと待ってください。年齢を計算する方法を教えてくださいよ。
博■おぉ,すまん。クエリで計算をする場合は,[フィールド]の欄に式を打ち込めば良い。そうすると計算結果が表示される。年齢の場合の式はこれじゃ。
 DateDiff("yyyy",[生年月日],Now())
博■DateDiffは,二つの日付の時間間隔を指定した形式で返す関数じゃ。ここでは,生年月日フィールドの日付から現在(Now関数)までの時間間隔を年数で返すという意味になる。
石●なるほど。
博■もし石橋君が年齢に抽出条件を付けたいと思うなら,例えば,30歳以下でなきゃいやだという場合は,[年齢]フィールドの[抽出条件]に式を打ち込めばよい。これもパラメータ・クエリにするなら,
 <=[年齢の上限を入力]
と入力すればよい(図15)。
図15●「年齢」フィールドに計算式とパラメータ・クエリを記述したところ
[画像のクリックで拡大表示]
石●そうすると,性別と年齢の二つのフィールドに抽出条件を設定したことになるのですが,それらの関係はどうなるんですか?
博■おぉ,またまた鋭い。各フィールドに設定した抽出条件はAND(論理積)の関係になる。また,同一のフィールドに複数の条件を設定することができて,それはOR(論理和)の関係になる。
石●さすが,博士。何でも知っていますね。
博■いや~,それほどでもあるよ。
石●…。
 すぐに調子に乗ってしまうのが悪い癖ですが,さすがは博士。見事に課題をクリアし,複雑な条件でのレコード抽出をAccessで実現しました。
 普段,Excelをガンガン使っているユーザーにとって,Accessの操作や用語には違和感があるかもしれません。私もそうでした。Accessを使わなくても,Excelで十分と思っている方も多いでしょう。確かにAccessでなければ処理できないというケースは多くないかもしれません。しかし,Accessを使えば,Excelよりも簡単に処理できるケースも数多くあるのです。利用できる選択肢は多いにこしたことはありません。便利なソフトは臆することなく挑戦し,活用するようにしましょう。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-28 10:43:51 | 显示全部楼层
初めてのデータベース設計


 一部の組み込み系システムを除けば,業務系システムはすべて何らかのデータベースを使っており,データベースを中核にしてシステムができあがっています。データベースを押さえることは,システムの中核を押さえることにほかなりません。したがって,データベースをどのような手順で,何に基づいて設計するのかを知っておくことは,システム構築に携わるすべての人にとって不可欠です。
 Part4は,データベース設計の上流工程である概念設計と論理設計にフォーカスして説明します。こうした作業はデータ・モデリングと呼ばれます。業務要件定義を解きほぐして,システムの中核となるデータベースの論理構造を決定することが目的です。
 データ・モデリングの重要性については,私たちが取り扱うビジネス・システム(業務システム)が,台帳中心のシステムであるということを考えれば明らかです。江戸時代などの時代劇を見ていると問屋の番頭が蔵の中で帳簿をつけている光景を目にすることがありますが,それは在庫台帳であったり,顧客台帳だったりします。現代になってこれらの作業の多くはコンピュータで機械化されました。しかし,台帳にデータを蓄積し,要求に応じてそこからデータを取り出し,加工し,必要とする人に配布するという本質のところは変わっていません。データ・モデリングはいわば,どのような台帳を作るのか,というのを決める作業なのです。
モデリングは3段階で行う Part4では,データ・モデリングは初めて,という方を対象に,簡単な事例に基づいてデータ・モデリングの手法について説明していきます。前提とするのは,テーブル,正規化といったリレーショナル・データベースの基礎知識だけです。
 ここではモデリングの記法としてER(エンティティ・リレーションシップ)図を使います。ここでちょっと注意していただきたいのは,ER図はテーブル間の関連を描くためだけのものではないということです。販売管理や物流といったターゲット業務の姿を,わかりやすく表したものであり,例えば,業務を改善するための分析などにも利用できます。とは言え,もちろん,データ・モデルですべてを表現できるわけではありません。
 最初に,モデリングの手順と,ER図における記法および用語について説明しておきましょう。一般にモデリングは,概念モデル作成(概念設計),論理モデル作成(論理設計),物理モデル作成(物理設計),の順に進めます。三つのモデルについて,ここでは次のように定義します。
概念モデル
業務概要に基づいてエンティティ(実体)などを抽出して,それらの間の関係を描いたモデル。実業務の中からトップダウン方式でエンティティをとらえたものです。

論理モデル
概念モデルをシステム化の対象範囲に限定して詳細化したモデル。システムで必要な画面や帳票イメージなどからデータ項目を1個1個拾い出すボトムアップ方式で定義していきます。業務システムで必要とするデータ項目がすべて定義されたモデルです。

物理モデル
パフォーマンスやプログラム処理などの観点から論理モデルの見直しを行ったモデル。リレーショナル・データベース(RDB)を前提とすれば,データ構造は論理モデルと同じと考えても良いでしょう。このモデルからRDBへ実装するための定義体が出力されます。
 この記事では,冒頭で述べたように,概念モデル作成と論理モデル作成について取り上げます。物理モデル設計では,パフォーマンス上の問題などで論理モデルを変更する場合もありますが,ほとんどのケースで論理モデルをそのまま物理モデルとしても問題ないからです。
モデリング記法の基礎を押さえておこう ER図の基本的な考え方は,モデリングの対象を,エンティティ(Entity,実体)と,エンティティ間のリレーションシップ(Relationship,関連)で表すということです。
 まずエンティティについて説明しましょう。モデリングの記法には色々ありますが,今回は市販のモデリング・ツールなどに広く採用され,普及しているIDEF1X(アイデフワンエックス)を用います。
 図1上を見てください。「社員」というエンティティは,属性(attribute,性質)として,社員番号,社員名,入社年月日を持ちます。これらの属性のうち,社員を特定できる属性を主キー(あるいは識別子)と呼びます。普通の企業では,社員番号を指定すれば対応する社員がただ一人に決まりますね(社員名は同姓同名がいるかもしれません)。したがって,社員番号が主キーです。主キー以外の属性(ここでは社員名,入社年月日)を,非キー属性と呼びます。
図1●ER図における基本的な記法(IDEF1Xの場合)
 ER図では,エンティティを四角形で表します。四角形を二つに区切って,主キーを四角の上段に,非キー属性を下段に書きます。
 エンティティとしてもう一つ,ある社員の「家族」を取り上げてみましょう。家族の構成メンバーには,その社員の社員番号のほかに,家族内で番号をふった家族番号,家族氏名,性別,生年月日,などの属性を持たせます。これらのうち,社員番号と家族番号がペアで主キーになり,残りの属性が非キー属性になります(図1下)。
 あるエンティティの属性のうち,ほかのエンティティの主キーになっているものを,外部キー(Foreign Key)と呼び,属性の後ろに(FK)を付けて表します。この例では,家族エンティティにおける社員番号が外部キーです。
 次は,リレーションシップについて見ていきましょう。一般に社員には家族が複数います(1対nの関係)。このことを,社員エンティティの四角形と,家族エンティティの四角形の間を線で結ぶことによって表します。このときn側にあたる家族エンティティ側には黒丸をつけます。社員エンティティの主キーである社員番号が,家族エンティティの外部キーになっていることから,社員エンティティを親エンティティ,家族エンティティを子エンティティと呼びます。
 リレーションシップの対応は1対nとは限りません。1対0,1対1などが考えられます。IDEF1Xのリレーションシップではこうした詳細な対応関係をカーディナリティによって規定します。
 図1において,黒丸の横に指定がない場合,1対「0以上」となります。これは,家族が一人もいない社員および複数人の家族を持つ社員が存在することを表します。
 1対「1以上」,すなわち社員には必ず家族が1人以上いることを表すには,黒丸の横に「P」を書きます。「Z」は,1対「0または1」の意味になり,社員は,家族がいないかまたは家族が1人であることを表します。1対nの「n」の値が決まっているときには,その数字を黒丸の横に記入します。すなわち家族は必ず3人と決まっていれば,黒丸の横に「3」を表記します。カーディナリティを最初から厳密に決めるのは難しいのですが,業務を正確に映し出すという点ではできるだけ,定義していくことが望ましいでしょう。
 最後に,リレーションシップとエンティティの種類について説明しておきます。家族エンティティの四角形の角が丸くなっていることにお気づきになったでしょうか。こう表記したエンティティは従属エンティティ(あるいは依存エンティティ)と呼ばれます。従属エンティティは,親エンティティ無しでは存在できないことを表しています。
 このER図で扱う家族には,必ず対応する社員がいます。すなわち,社員なしで家族が存在することはありません(このER図では社員の家族だけを表記していますから当然です)。したがって,家族エンティティは,従属エンティティになります。従属エンティティに対するリレーションシップは実線で表し,これを依存リレーションシップ(依存関係)と呼びます。
 これに対し,ほかのエンティティに依存しないエンティティを独立エンティティと呼びます。独立エンティティに対するリレーションシップは破線で表し,非依存リレーションシップ(非依存関係)と呼びます。依存関係と非依存関係を表現できるのは,IDEF1X記法の特徴の一つです。
問題:図2に会社とプログラマの関係を2通り,書いてみました。それぞれが何を表しているかを読み取ってください。

図2●下の2通りのモデルの意味の違いを読み取れますか?
 図2の上は,会社と雇用関係を結んでいる“社員プログラマ”を表します。従属エンティティであるプログラマは,親のエンティティである会社無しでは存在できません。会社が倒産したら失業しちゃう,と考えていただければ良いでしょう。これに対して下のモデルでは,会社とプログラマが非依存関係で結ばれています。さらに,子エンティティも親エンティティと同様,独立エンティティとなっています。そうです,この場合は,会社と契約を結んで仕事をしている“フリー・プログラマ”を想定しています。ある契約先の会社が倒産してもほかの会社と契約していれば稼げるので,会社から独立しているというわけです。

■モデリング・ライブ ここからは事例を通してモデリングを“ライブ中継”していきます。ぜひ,皆さんもご一緒にモデリングの世界を体験してください。事例は,図書館の貸し出しシステムを取り上げます。図書館の貸し出し・返却業務は,ER図の事例としてよく取り上げられていますが,ここではインターネットでの検索・予約と地域内(市内)での図書館の間での蔵書の移動を可能とします。「図書館相互予約・貸し出しシステム」とでも名付けましょう。
 皆さんがお住まいの地域の図書館で,すでに同じようなサービスをしているかもしれませんね。あるいは,こんなサービスがあったらうれしいのに,ということもあるかと思います。そんな身近な事例を取り上げましたので,自分自身が設計者になったつもりで考えてみてください。それでは始めましょう。
対象業務の概要を把握する Z市には,全部で20の図書館があります。市民は,自分の住んでいる地域の図書館で利用者登録をして,利用者カードを発行してもらうことにより,書籍やDVDの借り出しが可能となります。
 書籍は,同時期に1人で3冊まで借り出せます。借り出しの手続きは,利用者登録をした図書館で行いますが,借りたい書籍が無い場合には,市内のほかの図書館から移送して借りられます。どの図書館にも無ければ,オンラインで購入申請ができます。図書館では購入依頼に基づいて,契約した書店に注文します。
 利用者はインターネットで書籍を検索し,いずれかの図書館にあれば,その場で図書館を指定して予約できます。書籍がすべて貸し出されていたら,予約待ちの登録ができます。予約した書籍が貸し出し可能になると利用者にメールが届くので,図書館で貸し出し手続きを行います。
 これ以外の業務上のルールなどを定義しておきます。
書籍は図書館ごとに購入するので,同じ書籍が複数の図書館に存在します。ベストセラーなど貸し出しの多い本は,一つの図書館でも複数冊購入します。
インターネットから予約や予約取消はできますが,貸し出しはできません。
予約はインターネットのほか図書館窓口でも行えます。
予約無しでも書架にあれば借り出すことができます。
予約された本は,図書館司書が予約棚に保管し,貸し出し手続きを待ちます。
図書館によっては,書籍以外にもCD,DVD,紙芝居の貸し出しを行っているところがありますが,今回のシステムでは対象としません。
 これらの業務概要に基づいて業務フローや,システムの概念図(図3)をまとめます。これらの図は,必ずしも最初から正確に書く必要はありません。フリーハンドでざっと描いてみて,業務概要と照らし合わせて加筆・修正していけばよいのです。
図3●業務概要から導き出したシステム概念図 [画像のクリックで拡大表示]
トップダウンで概念モデルを作成する モデリングの最初のステップは,業務フローやシステム概念図に基づいてエンティティを抽出し,概念モデルを作ることです。概念モデルではトップダウンでエンティティを抽出していきます。
 では,エンティティとして何を抽出すれば良いのでしょうか。エンティティとは実体という意味ですが,もう少し具体的に言えば人,組織,設備,物や,アクティビティ(業務機能)を表す管理実体となります。エンティティは最終的にデータベースの中に実装されるものであり,RDBであればテーブルに相当します。
 と言っても,データベースに実装されるテーブルを想定してエンティティとして抽出しなさい,といきなり言われても困りますよね。すべての物事には手順が必要です。
STEP1:リソース・エンティティを抽出する 図3をご覧ください。エンティティの候補は,業務概要やシステム概念図から抽出していきます。その際,エンティティには大きく,リソース・エンティティとイベント・エンティティの二つのタイプがあります。それぞれのタイプごとに抽出する点に注意してください。
 リソース・エンティティは,業務を遂行するために必要な人,物,お金などのリソース(資源)のことで,業務概要の中では多くの場合,名詞として現れます。例えば,「市民」は,自分の住んでいる地域の「図書館」で「利用者」登録をし,利用者カードを…,といった具合です。
 ここでは,業務概要に登場する「市民」「図書館利用者」「予約者」「貸し出し者」「図書館司書」がリソース・エンティティの候補になります。このうち予約者と貸し出し者は,図書館利用者に対して役割(ロール)に基づいた別の呼び方をしたものです。ここでは利用者として一つにまとめておきます。
 利用者は市民のうち利用者登録をした人ですから,予約者や貸し出し者の関係と似ています。しかし,利用者であるか否かは,利用者登録をしているかどうかで重要な要因となりますので,別のリソースとして切り出しておきます。
 ほかに取引先・会社などとして「市」「図書館」「書店」,物品・設備などとして「書籍」「CD」「DVD」「紙芝居」などがエンティティ候補として挙げられます。CDやDVDはシステムの対象外としていますが,概念モデルはシステム化の範囲以外も対象とするのが一般的なので挙げておきます。ここで抽出したリソース・エンティティをまとめると図4のようになります。
図4●抽出したリソース・エンティティ
STEP2:リソース・エンティティ間を関連付ける 次に,抽出したエンティティ間の関連を調べ,必要に応じて,1対1,1対nなどの関連を定義していきます(このステップの成果物は図5)。
図5●リソース・エンティティ間の関連
[画像のクリックで拡大表示]
 例えば,市には多く(1人以上)の市民が居住していますので,市と市民の関係は1対nでカーディナリティは「P」となります。市民は1回だけ利用者登録ができますが,市民でも利用者登録していない人もいますから,市民と利用者の関係は,1対1または1対0となります。すなわちカーディナリティは「Z」です。
 リレーションシップには依存関係と非依存関係の2種類があります。例えば,市は現在20の図書館を所有しています。市が無くては(市立)図書館は存在しませんから,依存関係(実線)です。一方,市民はその市に“たまたま”住んでいるのであって,市からは独立して存在しますから,非依存関係(破線)です。
 もっとも依存関係/非依存関係について,今の段階で神経質になる必要はありません。必ず一緒に存在する強い関係の場合に依存関係にしておけば十分でしょう。関連の種類は,後に主キーを定義していく段階で明確になります。
 リレーションシップの種類としてもう一つ,分類エンティティについて触れておきましょう。システムで対象とするのは書籍だけとしましたが,先ほどCDやDVDもエンティティの候補として抽出しました。それらを「貸し出し品」エンティティとしてまとめて表現します。これを「汎化する」と言います。
 次に,貸し出し品と図書館の関係について見てみましょう。図書館は,多数の書籍を含む貸し出し品を所蔵しています。また,貸し出し品は,複数の図書館で所蔵されています。よって,関連はm(多)対n(多)となり,両方のエンティティに黒丸を付けて標記します。
 利用者と書籍の関係にも注目してください。利用者は書籍を3冊まで借り出せますので,書籍との関連は,1対nとなります。逆に書籍から見ると,誰からも借り出されずに図書館に置かれたままの場合もあります。すなわち,書籍から見た利用者が無いという状態があり得るわけです。よって,利用者対書籍の関係は,「0または1」対nとなります。利用者側の0を表すのが,オプショナル記号の◇です。これは非依存関係に対してだけ指定できます。
 エンティティ間にリレーションシップを引いたら,どんな関係があるのかの説明も併せて記述しておきましょう。IDEF1Xでは,これを動詞句と呼びます。動詞句をうまく設定すると,エンティティと合わせて文章になるはずです。
 図5は,このようにして作成したモデルです。ここまで文章で長々と書いてきたことがすっきりと表現されています。慣れてくると,この図を見ただけで,エンティティ間の関係をさっと理解できるようになります。
STEP3:イベント・エンティティを抽出して関連付ける 次に,もう一つの種類のエンティティである,イベント・エンティティ候補を抽出しましょう。このステップでの成果物は図6になります。
図6●イベント・エンティティと,それらの間の関連
 イベント・エンティティとは,企業活動を構成するアクティビティです。業務フローを描いたときに現れる処理単位や,システム概念図に現れます。業務概要を説明した文章では,図書館に書籍を「予約する」や,図書館から書籍を「借り出す」など動詞表現に着目するとわかりやすいでしょう。動詞はとりも直さず,「××する」という企業活動を指しているからです。
 本事例では,次のアクティビティが抽出できます。
利用者登録する
予約する
貸し出す(借り出す)
返却する
購入を依頼する
これらの動詞を名詞に置き換えると,「利用者登録」「予約」「貸し出し」「返却」「購入依頼」のエンティティが抽出できます。
 予約と貸し出しは,「0または1」対「0または1」の関係です。予約無しでふらっと図書館に行って借りてくることも可能だからです。これを予約側のオプショナル記号で表示します。また予約して,まだ借り出していないという状態もあります。これは貸し出し側のカーディナリティを「Z」として表現します。
 1回の予約で最大3冊の書籍が借り出せますので,予約書籍という依存エンティティを新たに設けて,1対「1以上」という関係を定義しました(カーディナリティは「P」)。貸し出しについても同様に考えて,貸し出し書籍エンティティを依存関係で追加しました。
 予約書籍と貸し出し書籍を別に設定したのは,予約した書籍をすべて同時に貸し出すわけではないからです。3冊予約したうち,2冊だけ貸し出し可能のメールが届き,図書館へ借りに行くといったケースもあります。ER図では,四角形(エンティティ)の有無や線(リレーション)の引き方で,そこまで深く表現できるわけです。
 予約書籍と貸し出し書籍の関係付けをしなくて良いのかと思われた方もいるかと思います。良い指摘ですが,この段階では,そこまでの関連付けは考えないでよいでしょう。ボトムアップ分析で属性が出てきたときに考えれば良いことにして先に進みましょう。
 次に,貸し出しと返却に注目してみましょう。貸し出したものは,必ず返却するというのがルールですが,時々返却を忘れている人がいるのも事実です。そこで,1対「0または1」関連としました。カーディナリティは「Z」になります。
 利用者登録と購入依頼は,ほかのエンティティとの関連は無しとしておきます。さらに,希望する書籍が利用図書館に無い場合は,市内のほかの図書館から移送できるということに対応するために書籍移送というエンティティを追加しました。
 ここで抽出したイベント・エンティティとその間のリレーションシップを表記すると図6のようになります。イベント・エンティティは,左から右へアクティビティの発生順に配置すると,業務の流れに沿うのでモデルが読みやすくなります。
STEP4:二つのエンティティ・モデルを統合する 次に,リソース・エンティティとイベント・エンティティを統合します。具体的には,リソース・エンティティ同士のリレーションシップを,イベント・エンティティを介して関連付けていきます。
 例えば,図5(STEP2)では利用者と書籍の間に「借り出す」という関連付けがされていましたね。実は,この二つのエンティティの間には,貸し出しというイベント・エンティティが存在しているのです。それを表すため,利用者と貸し出しの間に「借り出す」という関連付けをし,貸し出しの子エンティティである貸し出し書籍と書籍の間に「貸し出す」という関連付けを行います(図7)。
図7●イベント・エンティティを介してリソース・エンティティを関連付ける
 ほかにも,リソース・エンティティの間がイベント・エンティティを介して関連付けられることは多いのです。こうして,リソースとイベントのエンティティを統合して作成したのが図8です。いかがでしょうか。このモデルから「図書館相互予約・貸し出しシステム」の全体像がぼんやりながらイメージできるようになってきたのではないでしょうか。
図8●イベント・エンティティとリソース・エンティティを統合して作成した概念モデル
[画像のクリックで拡大表示]

概念モデルに属性を追加する 概念モデルにエンティティの属性を追加していくことにより,エンティティ間の関連がより鮮明になってきます。その際の手順について説明しましょう。
 エンティティの数が増えてきましたので,今後は,予約・貸し出し業務に焦点を当て,そのほかのエンティティ(市,市民,利用者登録,図書館司書,書店,購入依頼)は除外して考えます。このように全体モデルからある部分に絞り込んだものをサブジェクト・モデルと呼びます。
STEP1:トップダウンで主要属性を定義する まず,各エンティティに主キーを設定していきます。主キーは,一つのエンティティに蓄えられるデータ(インスタンスと呼びます)を,一意に識別するための属性です。
 リソース・エンティティから見ていきましょう。書籍エンティティは書籍1冊1冊を管理するものですから,書籍名,著者名,出版社などの属性が考えられます。書籍の主キーには,同一書籍名があることを考えて,書籍についているISBN番号を選びます。
 書籍エンティティには,ISBN番号を主キーに設定できましたが,主キーに選べるような適当な属性が実在しないこともあります。そうした場合には,新たに番号やコードを設定します。例えば,図書館エンティティの主キーはどうでしょうか。市内の図書館は,たかだか20ですので,図書館名で識別できるかもしれません。しかし,図書館名を主キーにすると,名称変更があったときに面倒です。
主キーは
・できるだけ値が変わらないもの
・データ長があまり長くないもの
を選択することが望ましいのです。
 そこで,図書館エンティティの主キーとして,図書館番号という属性を設定します。こうして設定した主キーは,その値をどう設定するのかを決めておく必要があります。例えば,1からの連番にしたり,すでに既存の別のシステムで使われているコードがある場合にはそれを流用するといったことが考えられます。
STEP2:主要属性付加に伴ってモデルを見直す 主キーをはじめとする主要属性を付加するのと並行して,モデルの関連付けを追加したり,修正していきます。図8の図書館と書籍の関係を見てください。図書館には,複数の書籍が所蔵されており,書籍は複数の図書館で購入・所蔵されているので,多対多(m対n)の関係ですね。
 一般にm対nの関連は,間に一つエンティティを置くことで1対n関係に帰着できます。ここでは,図書館と書籍の間に,蔵書というエンティティを新たに設けて,図書館と書籍の両方から関連付けをします(図9)。蔵書は図書館ごとに所蔵されている書籍を表しており,主キーである蔵書番号により1冊1冊を区別します。例えば,同じ書籍「蹴りたい背中(綿矢りさ 著)」を一つの図書館で2冊蔵書にしておく場合には,それぞれに異なる蔵書番号を振ります。
図9●蔵書エンティティを追加することで,図書館と書籍の多対多(m対n)関係を,二つの1対多関係に帰着させる
 親エンティティの主キーは,子エンティティでは外部キー(FK)として取り込まれます(これを「移行される」と呼びます)。子エンティティに移行された外部キーは,親エンティティを参照する際のキーとなります。
 改めて,書籍と蔵書の1対n関連に注目してください。この関連を定義することにより,親エンティティの書籍のISBN番号が子エンティティの蔵書の外部キーとして定義されます。この外部キーにより,例えば,蔵書番号1は,ISBN番号が4-309-01570-0,書籍名「蹴りたい背中」であることがわかります。ほかの蔵書番号の蔵書についても,ISBN番号が同じなら,同じ書籍であることがわかります。
 次に,図書館と予約の関連を見てみましょう。図8の概念モデルでは,図書館と予約は関連付けられていません。しかし,属性を考えることにより,依存関係を付けられます。具体的には次の通りです。
 利用者が予約すると,図書館ごとに予約番号が付加されて予約が成立します。このことから,予約エンティティでは,図書館番号と予約番号が主キーになることがわかります。したがって,予約エンティティは,図書館エンティティから依存関係で結ばれた依存エンティティになります。ほかにも,図書館と利用者など,図8のモデルでは,関連付けがもれていたところについて見直していきます。
STEP3:業務をさらに分析して関連やエンティティを修正 蔵書というエンティティが追加されたことに伴い,新たなエンティティを追加する必要が出てきました。それは蔵書予約割り当てエンティティです。
 予約業務の概要について説明しておきましょう。利用者から予約の要求があったときには,利用者が住んでいる地域の図書館の蔵書から優先して予約を取り決めることとします。その図書館の蔵書が貸し出せない場合は,ほかの図書館の蔵書を予約して移送してもらいます。その日数を考慮して貸し出し可能予定日を決定します。ただし,借りている人の返却が遅れることがあり得ますので,貸し出し可能になったら,利用者にメールでお知らせします。メールを受け取った利用者は,自分の地域の図書館に行き,予約番号を伝えて,貸し出し手続きを行います。
 この業務概要から,予約してから貸し出すまでの間に,どの図書館のどの蔵書を貸し出すかを割り当てておく仕組みが必要であることがわかります。そのため,予約と蔵書の両方に関連付けられたエンティティとして,蔵書予約割り当てを新たに作成します。貸し出し時点では,どの蔵書を貸し出すかが決まっていますので,貸し出し書籍エンティティに関連付けるエンティティを,書籍から蔵書へと変更します。
 次に,書籍移送のモデル表現について考えます。書籍移送は,蔵書がある図書館から移送されて別の図書館に移入される状態ですから,蔵書の依存エンティティになります。次に,移出元,移送先のリレーションシップを図書館から引くわけですが,このままでは書籍移送エンティティの属性として図書館番号が二つ現れてしまいます。一つのエンティティ内に同じ属性名は二つ置けません。そこで,一つは「移出元」,もう一方は「移送先」と名前を変えておきます。こうして命名した属性名をロール名と呼びます。
 図10は主要属性を付加したうえで,リレーションシップやエンティティを見直して作成したモデルです。図8との違いを確かめてください。「もう少し早い段階で属性を定義した方が,業務のイメージが描きやすい」と思われた方もいらっしゃるでしょう。実はその通りです。この記事は標準的な手順を説明しますが,現実には,エンティティを抽出すると同時に属性を定義したり,主キーを仮決めしたりすることもあります。
図10●図8に主要属性を付加したうえで,リレーションシップやエンティティを修正したモデル [画像のクリックで拡大表示]

      ボトムアップで論理モデルを作成する ここまでは,対象業務とシステム化への要件からエンティティを抽出し,エンティティを識別するための主キーやそれ以外の属性を決めて概念モデルを作成してきました。これはトップダウンの方法です。
 これに対して論理モデルの作成は,画面イメージや実際のユーザー操作を想定してエンティティや属性を抽出するという,ボトムアップの方法で行います。実際のプロジェクトではこの段階で,画面の紙芝居を作成して,ユーザーに見せながら「この画面ではこんな項目が必要だ」とか「画面の構成はこうで,オペレーションはこんな感じで」と仕様を詰めていきます。
 概念モデルは,対象範囲を広くとらえていました。一方,ボトムアップで作成する論理モデルは,概念モデルの全範囲を対象とすることもありますが,全体の一部であるサブシステムに相当する部分に焦点を当てて掘り下げていく場合が多いようです。ここで行う論理モデル作成では,予約・貸し出し業務にフォーカスします。
 インターネットでの書籍検索から予約までの画面のイメージを図11に示しました。利用者登録をした人が,自宅からインターネットで操作することを想定しています。大きな流れは次のようになります。
(1)利用者番号,パスワードを入力します。
(2)借り出したい書籍を書名,著者名などから検索します。
(3)検索した書籍が,どこかの図書館にあれば,条件に合致した書籍が一覧表示されます。
(4)予約したい書籍をチェックして,予約カゴに入れます。予約カゴの一覧を表示して予約ボタンを押すと予約でき,予約番号が表示されます。

図11●インターネットでシステムを利用する際の画面イメージ
[画像のクリックで拡大表示]
 (2)で検索した書籍が,市内のどの図書館にも無ければ,(5)の画面に移り,購入申請できます。加えて,(3)の検索結果画面で書名をクリックすると,(6)の画面に移り,その書籍がどの図書館にあって,貸し出し中かどうかを見ることができます。
 ここでは,分析対象画面を(4)の予約確認画面と,(6)の書籍詳細画面に絞って,エンティティや属性を拾い上げます。モデルを作成するにあたっては,正規化の手法を使います。正規化については,本書70ページの第3部Part2で解説していますから,ここでは詳細を触れません。各画面のデータ項目を,外部キーを除いて,同じ項目が二つ以上のエンティティに存在しないように振り分けていけば良いのです。重複を排除するように属性の配置を行っていけば,自動的に正規化されたモデルができるというわけです。
 図12はそれぞれの画面についてデータ項目を抽出し,第三正規化まで行った結果です。過程をざっと説明すると次のようになります。ぜひ自分で手を動かして図を作成し,確かめてください。
(1)書籍詳細画面からISBN番号を主キーとする書籍エンティティを,予約確認画面から予約番号を主キーとする予約エンティティを作成します。
(2)第一正規化を行い,書籍エンティティから蔵書エンティティを,予約エンティティから予約書籍エンティティをそれぞれ切り出します。
(3)予約書籍エンティティに対して第二正規化を行い,ISBN番号を主キーとする書籍エンティティを切り出します。
(4)第三正規化を行い,主キー以外の属性に依存する属性を別エンティティとしてまとめます(図12)。

図12●図11の書籍詳細画面,予約確認画面のデータ項目に対して第三正規化まで行ったところ [画像のクリックで拡大表示]
 ここまでできたら,画面単位に行ってきた個別のモデルを統合します。ここでは,書籍エンティティが共通していますから,これを一つに集約します。
 さらに,エンティティ間で関連付けを探ります。例えば,予約の予約番号は図書館単位で発番する体系となっていますので,予約を図書館の依存エンティティとします。図書館は利用者と1対nの関係にありますので,その関連を追加します。こうして作成したモデルの最終形が図13です。なお,ボトムアップでの画面から属性を拾ってエンティティにまとめ上げるときには多くの場合,概念モデルを参照することで効率よく作業を進められます。
図13●ボトムアップ手法により作成した論理モデル [画像のクリックで拡大表示]
トップダウン・モデルとボトムアップ・モデルを融合する いよいよ論理モデル作成の最終段階です。ここで改めて,トップダウンでの主要属性を付加したモデル(図10)とボトムアップで作成したモデル(図13)を見比べてください。トップダウン・モデルにあってボトムアップ・モデルに無いエンティティは,貸し出し,貸し出し書籍,返却,蔵書予約割り当て,書籍移送です。逆にボトムアップだけにあるのは,予約待ちですね。
 二つのモデルを融合させて,論理モデルを完成させましょう。結果は図14のようになります。ここまでくれば,RDBをどのように実装すればよいのか,はっきりしたイメージを把握できるのではないでしょうか。おおまかに言うと,各エンティティがテーブルに,エンティティ内の属性がそれぞれのテーブルの列名(フィールド)になります。
図14●図10と図13を融合して作成した論理モデル [画像のクリックで拡大表示]
☆          ☆          ☆
 駆け足でしたが,概念モデルでのエンティティの抽出や関連付けから,画面データを使ったボトムアップ分析による論理モデル作成までを実演してきました。モデリングに少しでも興味を持っていただいて,「データベース設計ってこんな風にやっているのか」という雰囲気をつかんでいただけたら幸いです。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-28 10:47:50 | 显示全部楼层
JavaでRDBとXMLにアクセスする方法


 Javaは,サーバーサイドのプログラミング言語として,すっかり定着した感があります。JSP(JavaServerPages),サーブレット(Servlet),EJB(Enterprise JavaBeans)などのJ2EE(Java2 Platform,Enterprise Edition)関連技術は,書籍や雑誌などで頻繁に取り上げられています。
 サーバーサイドJavaプログラミングを習得するとき,最初の一歩である「HelloWorld!」サーブレットを作り終えて文法を身に付けたら,次のステップは「外部データの読み書き」でしょう。プログラムは,蓄えた情報を外に記録したり,また外から新しいデータを読み込まなければ,常に同じ動作しかしません。ショッピング・サイトや会員制サイトなどの本格的なWebアプリケーションでは,商品情報や会員情報といったデータの読み書きが必須です。実用的なアプリケーションを作るなら,リレーショナル・データベース(RDB)を導入して,データを一元管理するのが一般的でしょう。最近では,インターネットでよく使われるXML(Extensible MarkupLanguage)ファイルをデータソース(データの保存先)として利用する機会も増えています。
 そこで,ここではサーバーサイドのJavaアプリケーションからRDBまたはXMLにアクセスする基本的なプログラミング・テクニックを,サンプル・プログラムを使って説明します。それぞれのメリット/デメリット,プログラミング・スタイルの違いなどをしっかり理解して,Web時代のデータソース・マスターを目指してがんばってください。
大量データの編集に向くRDB 環境間のやり取りに向くXML 新規にプログラムを作る場合,データソースとしてXMLとRDBのどちらを使うのがいいのだろう,と悩んでしまう方もいると思います。最初に,それぞれのメリット/デメリットについて比較してみましょう。
 サーバーサイドのプログラミングでは,
データソースに対して“スレッドセーフ”なアクセスを常に意識すべき
という決まりがあります。スレッドセーフ(thread safe)というのは,複数のプログラム(スレッド)が同時に同じデータソースへアクセスしても,データの不整合を起こさないというものです。
 RDBを利用する場合,RDBのデータベース・サーバーがデータの管理やアクセス制御などの仕事を肩代わりしてくれますので,比較的容易にスレッドセーフなプログラムを書くことができます。一方,XMLでは,プログラマが自分でスレッドセーフなコーディングをしなくてはなりません。完全にスレッドセーフなコードを書くのは,なかなか難しい作業です。スレッドセーフにすることでパフォーマンスが低下する可能性もあります。この点では,データベース・サーバーに任せる方が歩があると言えます。
 また,データの操作という観点で比べると,RDBでは,プログラム側でSQL*1文を発行して命令を出すだけです。実際の操作は,すべてデータベース・サーバーが行います。SQL文の書き方さえ覚えれば,コーディングは比較的容易でしょう。さらに,データベース・サーバーは一般にデータ量が多くなっても,データ操作の実行速度が落ちないように実装されています。そのため,データ数の増加によるパフォーマンスの低下が少ないという特徴があります。
 これに対してXMLでは,すべてのデータ操作をプログラム側で実装します。したがって,データ数が多くなればなるほど,パフォーマンスの低下が起こります。また,データ数の増加で操作が複雑になれば,コードも複雑になります。コーディングの難易度はRDBに比べてぐっと高いと言えるでしょう。
 一方,XMLには,国際的に標準化されたデータ記述形式であるというメリットがあります。そのため,異なるプラットフォームや企業間などでデータをやりとりをする場合に最適なデータソースと言えます。これに対しRDBはデータベース・サーバーの種類によって独自のバイナリ形式でデータを保存しますので,異なる環境間で直接データをやり取りするには不向きです。現実には,いったんXMLに変換してデータをやり取りするというケースも多いようです。
 また,導入コストの点からもRDBとXMLには違いがあります。XMLの実体は単なるテキスト・ファイルですので,ファイルの入出力ができる環境さえあれば,XMLをデータソースとして利用できます。しかしRDBは,サーブレット・コンテナなどのプログラムの実行環境とは別に,データベース・サーバーを導入しなくてはいけません。XMLに比べると,導入のコストや敷居が高くなります。
 以上を踏まえると,JavaでのRDBとXMLの使い分けのポイントは次のようになります。
  
<RDBを使うべき場合>
サーブレットの中で,頻繁にデータの更新を行う
データ数が多く,検索のパフォーマンスを重視する

<XMLを使うべき場合>
データ参照が主体で,データ更新の少ないアプリケーション
異なるプラットフォーム間でデータ交換を行う必要がある
データベース・サーバーを導入せずにデータ管理を行いたい
 例えば,RDBを用いる例としては,会員制サイトのユーザー情報などが挙げられます。サイトによってはユーザー数が非常に多くなることがあり,またブラウザを通して頻繁にユーザーの登録情報を更新することがあるからです。一方,XMLを用いる例としては,ショッピング・サイトの商品データなどが挙げられます。商品データが,ブラウザからの要求に応じて更新されるといったことはあまりありません。しかし,新しい商品データを別のプラットフォームから自動的に送信したい,というようなニーズはよくあります。
J2SEのコアAPIを使ってプログラミング RDBとXMLの違いが理解できたところで,それでは実際にプログラムを書いてみましょう。Javaには標準のコアAPIとして,RDBとXMLプログラミング向けのそれぞれのライブラリが用意されています(XMLについてはJ2SE1.4以降)。つまり,JavaのSDKさえあれば,他の拡張ライブラリなどをインストールしなくとも,すぐにRDB/XMLプログラミングを始められるわけです。ただし,RDBプログラミングでは,さらにデータベースごとのJDBC*2ドライバが必要です(図1)。JDBCドライバはJavaアーカイブ(jarファイル)になっていますので,それをクラスパス*3に設定します。
図1●JavaプログラムからRDBにアクセスする場合,RDBごとに用意されたJDBCドライバが必要
 RDB/XMLプログラミングのための標準パッケージは,それぞれ以下の通りです。
RDB:java.sql
XML:javax.xml
これらのパッケージの中に,RDB/XMLプログラミングに必要なクラスがほぼそろっています。
 なお,本稿で紹介するサンプル・コードは,Windows XP,J2SE 1.4.2_04,Tomcat 5.0.24,MySQL4.0.18,Connector/J3.0.11(MySQLのJDBCドライバ)で動作確認を行っています。それぞれのインストール方法については,WINGSプロジェクトのサイトに解説があります。必要に応じてご参照ください。サンプル・プログラムを動作させるには,日経ソフトウエアのWebサイトからダウンロードできるnikkei.warをTomcatの「%CATALINA_HOME%/webapps」フォルダ配下にコピーしてください*4。アプリケーションのルート・パスは「/nikkei」になります。
SQL文を実行してRDBからデータ取得 前述の通り,本来RDBとXMLとで扱うデータは,用途に応じて別々の性質のものになるのが普通です。しかしここでは,それぞれのプログラミング方法の違いを比較するために,あえて同じデータを扱うことにします。日本の作家の生年と没年が書かれた「作家情報」をサンプルとしてRDB/XMLのそれぞれで扱ったプログラムを作ることにしましょう。「作家情報」のテーブルは,表1のようなレイアウトにします。
表1●サンプル・データとして利用する日本の「作家情報」のテーブル・レイアウト
 まずは,RDBの場合から。データベース上にある「作家情報」テーブルを,サーブレットから読み出して,ブラウザに表示してみましょう(リスト1*5。JavaからRDBを操作するのに最初に必要なのは,JDBCドライバの読み込みです。JavaのコアAPIに用意されているのは,どんなデータベース・サーバーでも使える汎用のクラス(インタフェース)群にすぎません。実際にデータベースを操作するのは,データベース・サーバーごとに個別に提供されるJDBCドライバです。リスト1の(1)では,Class.forNameメソッドを使って,MySQLのJDBCドライバのクラス名を取得しています。続いて,JDBCドライバを管理するDriverManagerクラスのgetConnectionメソッドを使ってデータベースに接続し,その情報をConnectionオブジェクト(con)に格納します(2)。このような接続方法を取ることで,個々のデータベース・サーバーに依存しないJavaプログラムを書くことができるわけです。
リスト1●RDBよりデータを読み込んで表示するサーブレット(一部)。(A)の文字列は,実際には1行で記述する
 Connectionオブジェクトは,Statementオブジェクトを生成します(3)*6。このStatementオブジェクトは,SQL文の実行と結果を返す役目をします。SQL文には,INSERT(挿入),UPDATE(更新),DELETE(削除)のように結果を返さない文と,SELECT(検索)のように結果を返す文とがあります。こうしたSQL文の種類に応じて,実行するメソッドは表2のように異なります。
表2●SQL文を実行するためのメソッド(Statementクラス)
[画像のクリックで拡大表示]
 リスト1ではSELECT文を使って,テーブル内のデータを取得しています(ここではSELECT *ですべてのデータを取得)。SELECT文を実行する場合,その検索結果はResultSetオブジェクトとして返されます(4)。ResultSetオブジェクトは,図2のように内部に検索結果を格納しています。また,getInt,getString,getDateなど,データ型に応じたgetterメソッドを備えています。検索結果を取得するには,(5)のようにこれらのgetterメソッドを用います。以上でデータベースからデータを取得し,その結果を出力できました。
図2●ResultSetオブジェクトに蓄えられている検索結果のデータ
 最後に,closeメソッドで,必要がなくなった時点でデータベースとの接続を閉じましょう(6)。もちろんJavaプログラマならご存知のように,放っておいてもいずれはガーベジ・コレクタ*7が自動的に接続を閉じてくれます。しかし,ガーベジ・コレクタが働くまでの間は,累積した使用済みの接続が,無駄にリソースを食い潰すことになります。closeメソッドは,いついかなる場合にも実行されるように,必ずfinally句の中に記述するようにしてください。

DOMでドキュメント・ツリーを生成 次に,データソースがXMLの場合のJavaプログラムを見てみましょう。「作家情報」データは,XMLで表すと,リスト2のようになります。今度はこちらをデータソースとして,先ほどと同じ動作をするサーブレットを書いてみます。
リスト2●XML形式で書かれた「作家情報」(writers.xml)
 XMLのプログラミングには,DOM(Document Object Model)とSAX(Simple API for XML)の2種類のAPIを使う方法があります。
 DOMは,XML文書全体をオブジェクトとして構成します。一方,SAXは文書を頭から読み込んでいって,XMLの要素ごとにイベントを発生させます。そのため,SAXはDOMに比べてメモリー消費量が非常に少なく,かつ高速に処理を行うことができます。しかし,文書全体に対して操作を行いたい場合,コードが複雑になってしまう欠点もあります。ここでは一般的によく使われるDOMを用いますが,データ量の大きいXMLを高速に読み込みたいときには,SAXを使う方が良いでしょう。
 リスト3が,DOMを用いたJavaプログラムです。なお,データソース(writers.xml)は,Webアプリケーションのルート配下の「WEB-INF/data」フォルダ以下に置かれているものとします*8
リスト3●XML文書を読み込んで表示するサーブレット(一部)
 まず,XML文書からドキュメント・ツリーを生成します。DOMは,ドキュメント・オブジェクト・モデルという名の通り,XML文書全体をオブジェクトとして表現します。XMLの構成要素はノード(Nodeオブジェクト)と呼びます。ノード同士が,ツリー状の構造を構成しているものが,ここで生成されるドキュメント・ツリーです。具体的には,DocumentBuilderオブジェクトを生成し,そのparseメソッドにXML文書のパスを指定してドキュメント・ツリー(Documentオブジェクト)を取得します。なお,DocumentBuilderオブジェクトを生成するためには,先にFactoryと呼ばれるオブジェクトを生成する必要があります。これらの一連の手順を実行しているのがリスト3の(1)です。
 writers.xmlのドキュメント・ツリーは,図3のようになります。DOMで生成されるドキュメント・ツリーでは,タグで囲まれた要素をElementオブジェクト,タグの属性をAttrオブジェクト,タグとタグではさまれた文字列部分をTextオブジェクトとして扱います。DOMでは,タグ間の空白部分(スペース,タブ文字,改行など)もTextオブジェクトとして認識します。図3の空の四角は,この空白部分のTextノードを表しています。
図3●DOMで生成されるXML文書のドキュメント・ツリー
[画像のクリックで拡大表示]
 DOMの役割は,ドキュメント・ツリーをルート要素から順に辿っていく(ノード・ウォーキングと呼びます)手段を提供することです。プログラムでは,それらの手段を通してドキュメント・ツリーを操作することで,データの取得/編集/削除を行います。
 リスト3では,(2)でgetElementsByTagNameメソッドを使って,タグの要素をすべて取り出します。その要素一つずつに対して,属性として与えられているIDをgetAttributeメソッドで読み出します(3)。そして,子要素である名前,生年,没年を,それぞれ読み出しています(4)。今回はコードの読み易さを重視して,getElementByTagNameメソッドを用いて子要素を取り出しています。しかし,このメソッドは,ノード・ツリー全体を走査してしまうためあまり効率的ではありません。実際にコードを書くときは,getChildNodesメソッドを使って目的の子要素を取り出すのが良いでしょう。
 プログラムを書き終えたら,RDB/XMLそれぞれのサーブレットを実行させてみましょう。ブラウザからサーブレットにアクセスすると,どちらも図4のように表示されるはずです。
図4●サーブレットの実行結果。RDBとXMLのどちらを利用する場合でも,同じ結果になる

データの追加/更新/削除もやってみよう リスト1とリスト3では,それぞれのデータソースからデータを読み出す(検索)処理を紹介しました。各データソースを扱うときの約束事(決まり切った手順)を,おおよそ理解できたと思います。今度は,データの追加/更新/削除といったデータ編集機能を追加してみましょう。具体的には,データソースに対して,以下の操作を行います。
作家データ(ID=4, 名前=中上健次, 生年=1946, 没年=1992)を追加
ID=3のデータの名前「平岡公威」を「三島由紀夫」に変更
ID=1のデータを削除
 RDBの場合は,リスト1でSQL文を作成/実行しているコードをリスト4のように変更すればいいでしょう。追加/更新/削除の操作は,データ検索(SELECT文)のときと同様に,SQL文を作ってそれを実行するという手順になります。ただし,これらの操作は結果を返しませんので,表2の通り,executeUpdateメソッドを使ってSQL文を実行します。RDBを使う場合は,データの編集処理も簡単です。
リスト4●RDBへのデータ追加/更新/削除を行うコード
 一方,XMLの場合はもう少し複雑になります。必要なデータを操作するだけでなく,ドキュメント・ツリーを再構成するといった処理が必要になるからです。コードは,リスト5のようになります。
リスト5●XMLへのデータの追加/更新/削除を行うコード(一部)
 (1)では,新しいデータを追加しています。最初にDocumentオブジェクトのcreateElementメソッドで新たな要素オブジェクトを生成していますが,それだけではドキュメント・ツリーは更新されません。appendChildメソッドを使って,新たに作ったオブジェクトを大本のドキュメント・ツリーに追加する必要があります。このように,DOMのプログラミングでデータの編集を行う場合,常にドキュメント・ツリーを意識して構造を組み上げていくのが特徴です。
 (2)は,データ更新を行う処理です。データ更新(子要素オブジェクトを取り替える)には,replaceChildメソッドを用います。第1引数が新しいノード,第2引数が古いノードである点に注意してください。データの削除は(3)のようになります。子要素の削除には,removeChildメソッドを使います。
 さらに,RDBと大きく異なるのが(4)の部分です。RDBでは,実際のデータ操作はデータベース・サーバーが行いますので,変更はすぐにデータソースに反映されます。ところがXMLでは,プログラム中でのデータ操作は,メモリー上に展開されたドキュメント・ツリーに対して行います。そのため,ドキュメント・ツリーを変更しただけでは,元のXML文書に反映されません。そこで(4)のように,変更したドキュメント・ツリーをXML文書に書き出す処理が必要です。
 データの追加/更新/削除を実行し,前述のリスト1またはリスト3のサーブレットで動作確認をしてみましょう。どのような表示がブラウザに現れるか,皆さんの目で確認してみてください。
☆          ☆          ☆
 ここでは,RDBとXMLについてのJavaプログラミング方法を一通り学習しました。データソースによって,プログラミングのスタイルがずいぶん違うことに気付かれたことと思います。RDBでは,SQL文をデータベース・サーバーへ送信することでデータ操作を行いました。一方XMLでは,文書をドキュメント・ツリーの形に変換して,要素(ノード)ごとに操作しました。RDBは,データベース・サーバーがデータを管理しているのに対して,XMLは単なるテキスト・ファイルである,という両者の違いが,そのままプログラミングのスタイルにも表れていると言えるでしょう。その違いを理解して,適材適所にデータソースを使い分けていくのが,Webデータベース・プログラミングのだいご味の一つです。
RDB, XML操作に役立つEclipseプラグイン
 最近では,Javaアプリケーションの開発には,オープンソースの統合開発環境であるEclipseを使うケースが多いようです。今回の記事では,RDB/XMLプログラミングの基本手順を学ぶことがメインでしたので,Eclipseを使いませんでした。しかし,Eclipseには,RDBやXML操作に役立つツールがプラグインとして提供されており,これらを活用するとより効率的にアプリケーションを開発できます。ここでは,そうしたプラグインの中から,著者が試してみて使い勝手が良かったものをいくつかご紹介します(表A)。基本的にフリーで使えるものばかりですので,皆さんも自分で実際に試してみてください。
表A●RDB,XML操作アプリケーション開発に役立つEclipseのプラグイン
[画像のクリックで拡大表示]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-28 10:54:07 | 显示全部楼层
.NETで,データベース・プログラムはこう作る
TableAdapterクラスがカギを握る


 Visual Studio(VS)2005では,.NETプログラミングのベースとなる .NETFrameworkのバージョンが2.0となり,データベース・アクセス機能であるADO.NETも,1.1から2.0へと機能拡張しています。これによって,データベース・アプリケーションの開発スタイルがどう変わるのかが気になる方は多いでしょう。実用的なアプリケーションを作るのにデータベース・プログラミングは欠かせませんが,米Microsoftはこれまでに何度もデータベースのアクセス手法を変更しており,多くの開発者が様々な影響を受けてきたからです。
 そこでここでは,実際にデータベース・アプリケーションを作成しながら,VS2005のデータベース関連の新機能を解説したいと思います。開発には,マイクロソフトが提供しているVisual C# 2005 ExpressEditionと,SQL Server 2005 Express Editionのそれぞれ日本語版を使います*1
新しくなったデータベース・コントロール 最初に,データベース・アプリケーションの開発で大きな役割を果たす,データベース・コントロール(Windowsフォームで使うもの)がどう変わったかを見てみましょう。一番の変更点は,前版のVS2003のデータベース関連コントロールのうち,データベースへの接続やデータ操作機能を提供する「xxxDataAdapter」「xxxConnection」「xxxCommand」のコントロール(それぞれSQL Server,Oracle,OLE DB*2,ODBC*3ごとに用意されており,xxxの部分に名称が入ります)がなくなったことです。VS2005では,従来と同じDataSetコントロールのほかに,「DataGridView」「BindingSource」「BindingNavigator」コントロールが新しく加わります*4図1)。
図1●Visual C# 2005 Express Editionのデータベース関連のコントロール
 データベース・コントロールの関係や役割は,ざっと図2のようになります。ADO .NET1.1の中核コントロール(DataAdapter,Connection,Command)が果たしてきた役割は,今後は「TableAdapter」クラスが担当するというわけです。しかし,TableAdapterクラスは,図1のツールボックスに見当たりませんね。実はTableAdapterクラスは“VS 2005が生成する”クラスなのです。詳しくは後で説明しますが,VS2005でのデータベース・アプリケーション開発で非常に重要な働きをするクラスであることを,まず覚えておいてください。
図2●Windowsプロジェクトで利用できるデータベース・コントロールの関係。VS 2005(b)では,VS 2003(a)の三つのコントロール(DataAdapter,Connection,Command)に代わり,TableAdapterクラスを使う
[画像のクリックで拡大表示]
 新たに加わったBindingSourceコントロールとBindingNavigatorコントロールは,それぞれ連携しながらデータ表示/操作の管理を行います。BindingSourceコントロールは,データソースになるDataSet(メモリーに格納されたテーブルの内容や情報)を一元的に管理します。そしてBindingSourceに接続しているBindingNavigatorコントロールに,カレント・レコード(現在操作しているレコード)の移動,追加,削除の機能を提供します。一方,BindingNavigatorコントロールは,カレント・レコードを操作するためのユーザー・インタフェース部品です。「次に移動」「前に戻る」「最初/最後に移動」「新規追加」「削除」「保存」といったボタンを備えています。
 BindingSourceコントロールは,DataBindingsプロパティ経由でBindingSourceにアクセスする他のデータ・コントロール(データ入力や表示を行うコントロール)にも,カレント・レコードの情報を提供します。これは,BindingSourceに接続しているすべてのコントロールが,同じDataSet上のカレント・レコードを参照することを意味します。したがって,BindingNavigatorでレコードの位置を移動させると,それに伴ってすべてのデータ・コントロールの表示内容も同時に変わります。反対に,データ・コントロールで入力・更新されたレコードの内容も,ほかのコントロールにすぐさま反映されます。
 そのほか,BindingSourceコントロールは様々なメソッドやプロパティを備えています。これらをプログラムで直接操作すれば,BindingNavigatorを使わずに,レコード操作を行うことも可能です。
 DataGridViewコントロールは,現行のDataGridコントロールの機能拡張版です。DataGridでは,画像イメージを含むカラム(列)を表示するにはカスタマイズが必要ですが,DataGridViewでは標準機能としてカラムの画像を表示できます。また,Rowsプロパティを直接指定することで,データソースとバインドしていないデータを表示することも可能です。プログラムでプロパティを操作すれば,表示したいセル位置(Row/Col位置)をグリッドの左上に移動させるといったこともできます。また,面白い機能としては,グリッドのデータをクリップボードにコピーする機能も追加されています。図3は,グリッドに表示したデータをクリップボードにコピーし,それを取得してラベルとして表示するアプリケーションです。クリップボードにコピーするボタンのイベント・ハンドラはリスト1の通りです。クリップボードへのコピーとデータ取得が簡単に記述できることがわかると思います。
図3●グリッドに表示したデータをクリップボードにコピーし,それを取得してラベルとして表示するアプリケーション


private void button2_Click(object sender, EventArgs e)
{
//ヘッダー部分はコピーの対象としない
dataGridView1.ClipboardCopyMode =
  DataGridViewClipboardCopyMode.
  EnableWithoutHeaderText;

//グリッドで選択されているセルの数を確認
if (dataGridView1.GetCellCount(
  DataGridViewElementStates.Selected) > 0)
{
  try
  {
   //グリッドからデータを取得し,クリップボードへ転送
   Clipboard.SetDataObject(dataGridView1.
    GetClipboardContent().GetData(DataFormats.Text));

   //クリップボードのデータを取得し,ラベルに表示
   label1.Text = Clipboard.GetDataObject().
    GetData(DataFormats.Text).ToString();
   label1.Visible = true;
  }
  catch (System.Runtime.InteropServices.
   ExternalException)
  {
   MessageBox.Show("クリップボードへの格納に失敗");
   label1.Visible = false;
  }
}
}
リスト1●図3のアプリケーションのbutton2のClickイベント・ハンドラ。DataGridViewは,データをクリップボードにコピーする機能を持つ

VS 2005が作り出すTableAdapterクラス 前版のVS2003では,データベースへの接続,取得したデータのDataSetへの格納,データベースへの変更の反映といった機能を,DataAdapter/Connection/Commandコントロールが担っていました。VS2005では,これらの機能をTableAdapterクラスが提供します。TableAdapterクラスは,.NET Framework2.0で提供されたクラスではなく,VS 2005が独自に作り出す,カスタマイズ可能なクラスです。
 TableAdapterを作成するには二種類の方法があります。一つは,データソース構成ウィザードを使う方法です。ウィザードで生成するには,DataSetを作成するための既存のデータベースが必要です。もう一つは,型付きの(型指定されたメソッド,イベント,プロパティを持つ)DataSetを作成し,データセット・デザイナ上でTableAdapterを作成する方法です。
 ここでは,プロジェクトに新しくデータソースを追加する形でDataSetとTableAdapterを作成してみましょう。接続するデータベースとして,SQL Serverなどのサンプル・データベースとしておなじみのpubsデータベース*5を使います。
 最初に,新規のWindowsApplicationプロジェクトを作成します。続いて,VS2005のメニュー[データ]-[データソースの表示](またはShift+Alt+Dキーを押す)を選択して,IDE上にデータソース・ウィンドウを開いてください。このウィンドウの中央にある「新しいデータソースの追加」をクリックすると,データソース構成ウィザードが起動します(図4)。
図4●データソースを追加するデータソース構成ウィザードの画面
 ウィザードのデータ接続の選択画面では,「新しい接続」ボタンをクリックして「接続の追加」画面を表示します。ここで「データソース」の部分を「SQL Serverデータベースファイル(SqlClient)」に設定し,データベース・ファイル名でpubsデータベース・ファイルを選択します(図5)。すると,データベース・オブジェクトを選択する画面に,pubsデータベース内のテーブルが一覧表示されます。ここでは,「publishers」テーブルを選択しましょう。データソース・ウィンドウに,pubsDatasetノードとその配下にpublishersノードが追加されます。
図5●「接続の追加」画面でpubsデータベース・ファイルを指定する
 このままpublishersノードをフォーム上にドラッグアンドドロップすると,publishersテーブルの内容をグリッドで表示するユーザー・インタフェースになります。しかし,ここではテーブルの内容を1カラムずつテキストボックスに表示することにしましょう。そのためには,publishersノードを選択し,右側に表示されるドロップダウン矢印をクリックし「詳細」を選択します(図6)。そしてpublishersノードをフォーム上にドラッグすると,フォーム・デザイナ上にBindingNavigatorコントロールと,テーブルの各カラムに対応するラベル・コントロール,テキストボックス・コントロールが自動的に配置されます。
図6●データソース・ウィンドウでpublishersノードの「詳細」を選択するところ
 フォーム・デザイナの下にあるコントロール・パレットに注目してください。pubsデータベースのDataSet(pubsDataSet),pubsDataSet内のpublishersテーブルへのアクセスを提供するBindingSourceコントロール,そしてpublisherテーブル対応のTableAdapterコントロールがそれぞれ配置されていることがわかりますね(図7)。
図7●フォーム・デザイナの下にあるコントロール・パレットには,DataSet(pubsDataSet)コントロール, BindingSourceコントロール,TableAdapterコントロール,BindingNavigatorコントロールがそれぞれ配置されている
 自動生成されたTableAdapterのプログラムでの定義は,ソリューション・エクスプローラ上のpubsDataSet.xsdノード配下にあるpubsDataSet.Designer.csで見ることができます。また,pubsDataSet.xsdをダブルクリックすると,データセット・デザイナが起動し,テーブルの情報を確認できます(図8)。
図8●publishersテーブルをデータセット・デザイナで表示したところ。「Fill,GetData( )」というメソッドがあるのがわかる
 これだけで,publishersテーブルの内容を表示するアプリケーションは完成です。VS2005やウィザードの操作を順番に行っただけで,1行もプログラム・コードを書いていません。しかしたったこれだけの操作で,実行すると1レコードごとに内容を表示するアプリケーションを作れたわけです。BindingNavigatorを操作すると,カレント・レコードを移動したり,データを更新することもできます。
SQL文はTableAdapterのメソッドに登録する では,今作成したアプリケーションの中身をもう少し調べてみましょう。Form_Loadイベント・プロシジャを見ると,次のコードが自動的に挿入されています。
 this.publishersTableAdapter.Fill(
   this.pubsDataSet.publishers);

つまり,フォームをロードすると同時に,TableAdapterクラスに実装されているFillメソッドで,pubsDataSet内のpublishersテーブルの中身を読み込んでいるわけです。
 再度データセット・デザイナに戻って,図8のpublishersテーブルの下にあるpublishersTableAdapterを見ると,「Fill,GetData()」というメソッドが確認できます。プロパティ・ウィンドウを見てみると,このメソッドの実体は,publishersテーブルからデータを全件取得するSELECT文であることがわかります(図9)。VS 2005では,プログラマがデータベース操作のためのSQL文をプログラム・コードに直接記述するのではなく,SQL文を格納したメソッドを呼び出すようになっています。
図9●メソッドの実体は,publishersテーブルからデータを取得するSELECT文である
 現実には,全部のデータではなく,特定の条件に一致したデータだけを欲しい場合が多いでしょう。こうした場合,TableAdapterクラスでは,条件付きのSQL文を新しい名前のメソッドに割り当てることが可能です。それにはTableAdapterを右クリックして表示される「クエリの追加」を選択して起動するウィザードを利用します。
 例えば,publishersテーブルのcountryカラムで絞り込みを行うSQL文,
 SELECT pub_id, pub_name, city, state, country
 FROM publishers WHERE country = @COUNTRY

をFillByCountryというメソッド名で割り当てます。WHEREの条件が「country =@COUNTRY」となっている点に注目してください。これにより,文字列型のCOUNTRYという名前の引数を持つFillByCountryメソッドが生成されます。そこで,Form_Loadイベント・プロシジャで,先ほどのFillメソッドの代わりに,
 publishersTableAdapter.FillByCountry(
   pubsDataSet.publishers, "USA");

と記述します。すると,COUNTRYカラムの値が「USA」であるデータだけを表示するようになります。このようにTableAdapterクラスは,VS 2005のデータベース・アプリケーション開発で中心的な役割を担う存在になっています。

複数テーブルの操作も簡単に作成できる これまでの例は,単一のテーブルからデータを取得するアプリケーションでした。今度は,リレーションシップ*6が定義された二つのテーブルを使って,マスター/詳細形式のフォームを持つアプリケーションを作ってみましょう。これも非常に簡単に作れます。
 新規のプロジェクトを作り,先ほどと同じ手順でデータソース(pubsデータベース)を作成します。ただし,データベース・オブジェクトを選択する画面で,今度は二つのテーブル(publishersとemployee)を選択してください。データセット・デザイナで見るとわかるように,この二つのテーブルはpub_idカラムで関連付けられています(図10)。つまり,publishers(出版社)が決まれば,その出版社に所属するemployee(従業員)が決まるということです。
図10●publishersとemployeeテーブルはpub_idカラムで関連付けられている
 pubsデータベースを追加したデータソース・ウィンドウを開いて,publishersとemployeeテーブルのノードを展開してください。するとpublishersテーブルの最後にemployeeテーブルのノードがあるのを確認できます。これはテーブルの従属関係を表しています。先ほどと同様に,publishersノードのドロップダウンリストで「詳細」を選択してから,フォーム上にドラッグアンドドロップしてカラム用のコントロールを作成します。次に,publishersノード配下のemployeeノードをそのままフォーム上にドロップします。
 たったこれだけの作業でマスター/詳細形式のアプリケーションが完成します。実行してみると,8件のpublishers(出版社)データに対して,それぞれに関連付けられたemployee(従業員)データがグリッドに表示されます(図11)。Form_Loadイベント・プロシジャに,publishersTableAdapterとemployeeTableAdapterクラスのそれぞれのFillメソッドを実行するコードが自動的に挿入されていることを確認してください。
図11●二つのテーブルの内容をマスター/詳細形式で表示するアプリケーション。プログラム・コードを1行も書かずに作れる
音楽CD管理アプリケーションを作ってみよう さて,ここからは,より実用的で本格的なデータベース・アプリケーションを作ってみましょう。できるだけ身近なサンプルということで,音楽CDのデータベース・アプリケーションの作成に挑戦します。
 最近流行の携帯オーディオ・プレーヤーには,音楽CDを管理するソフトが付属していて,プレーヤーに音楽を転送する機能のほかに,パソコン上での楽曲管理データベースの機能を提供するものが多いですよね。これらの楽曲管理ソフトの多くは,インターネット上のCDDB(CD情報データベース)サービスを使って,音楽CDの情報を取得し,データベースに反映させます。
 そこで,この仕組みを利用して,自分で楽曲を管理するデータベース・ソフトを作ってみようというわけです。CD-ROMドライブに挿入した音楽CDのアルバム・タイトル,楽曲名,アーティスト,ジャンルなどの情報を,インターネットのCDDBサービス経由で自分のデータベースに格納するプログラムです。
 CDDBサービスは,米Gracenoteが提供しています(図12)。同社が提供するCDDBへのアクセスは,非商用目的であれば,開発者登録を行うことで無償で利用することができます。提供される情報検索機能はプラットフォーム別になっていて,Windows開発向けにはCOM/ActiveXのライブラリ(SDK)が用意されています。まずはGracenoteサイトに開発者登録をして,SDKをダウンロードしておきましょう(カコミ記事「CDDBアクセス用SDKを入手するには」を参照)。
図12●CDDBサービスを提供する米GracenoteのWebサイト
[画像のクリックで拡大表示]
 ダウンロードできるSDKは,圧縮ファイル(CddbWindowsSDK.zip)になっています。解凍(展開)すると,図13のようなファイル/フォルダが展開されます。このうち,「CDDBControl.dll」がCDDBサービス・アクセス用のCOM/ActiveXライブラリです。このライブラリをインストールするには,「InstallControl.bat」を使います。Docsフォルダには,SDKのプログラミング・リファレンス(CDDB_SDK_2.0_Programmers_Reference.pdf)があるので,このファイルを参考に,CDDBサービスにアクセスするプログラムを開発します。
図13●CDDBにアクセスできるSDKの内容
.NET Framework 2.0のDriveInfoクラスでドライブ一覧を取得する CDDBライブラリをインストールできたら,まずCDDBの検索が可能かどうかをチェックするプログラムを作ってみましょう。
 CDDBアクセス・テスト用のプロジェクトを作成し,ソリューション・エクスプローラの参照設定ノードを右クリックして「参照の追加」をクリックしてください。「参照の追加」ダイアログが開いたら「COM」タブをクリックして「CDDBControl 1.0 TypeLibrary」を選択します。OKをクリックすると,参照設定ノードに「CDDBCONTROLLib」が追加されます(図14)。これでCDDBアクセス用ライブラリを利用できるようになりました。CDDBCONTROLLibノードをダブルクリックして起動するオブジェクト・ブラウザを見ると,CDDBCONTROLLibの名前空間とライブラリに含まれるクラス,インタフェース,メソッド,プロパティの情報を確認できるはずです。
図14●参照設定によってCDDBControlが利用可能になった
 CDDBライブラリの中心となるクラスは「CDDBControlClass」です。このクラスを使ってCDメディアの情報を取得し,取得した楽曲CD情報からCDのトラック情報を取得します。なお,CDメディアの情報を取得するには,使用しているパソコンのCD-ROMドライブ情報を取得する必要があります。CD-ROMドライブの情報は,Win32APIのマルチメディア関連関数である「mciSendString」と「mciGetDeviceID」を使うことにします。
 パソコン上のドライブ一覧は,.NET Framework2.0に追加された「DriveInfo」クラスのGetDrivesメソッドで取得できます。これを使えば,アプリケーションが起動した最初の段階でパソコン上にCD-ROMドライブがあるかどうかを確認できます。GetDrivesメソッドで取得できるDriveInfoクラスのインスタンスにはIsReadyプロパティがあるので,ドライブにCD-ROMが挿入されているかどうかの判定も可能です。
 では,CDDBアクセス・テスト用のプロジェクトを開き,フォーム・デザイナ上にボタン・コントロールとリストボックス・コントロールを配置してください。ボタン・コントロールをダブルクリックしてイベント・ハンドラのスケルトンを表示したら,ソースコードの先頭に移動し,usingディレクティブ部分に「CDDBCONTROLLib」名前空間を追加します。マルチメディア操作のWin32APIとドライブ情報(DriveInfo)を使うので「System.Runtime.InteropServices」と「System.IO」名前空間を追加する必要があります。次に,ボタンクリックのイベント・ハンドラのすぐ上に移動し,「mciSendString」と「mciGetDeviceID」関数を使うために,リスト2のように宣言します。

[DllImport("Winmm.dll")]
private static extern int mciGetDeviceID(string lpszDevice);

[DllImport("winmm.dll")]
private static extern int mciSendString(String command,
StringBuilder buffer, Int32 bufferSize, IntPtr hwndCallback);
リスト2●二つのWin32 API関数の宣言
 ボタンクリックのイベント・ハンドラには,リスト3のコードを記述します。簡単に処理内容を説明しましょう。最初にmciSendString関数でオーディオ・デバイス(cdaudio)をオープンします(1)。そのパソコンに複数のデバイスがある場合,どのデバイスをオープンしたのかがわかるように,デバイスにプログラム内だけで有効なエイリアス(別名)をセットします。次に,エイリアスを使ってmciGetDeviceID関数でデバイスのIDを取得します(2)。ここで取得できたデバイスIDをCDDBControlClassで利用します。デバイスの操作を終えたら,再びmciSendString関数でデバイスをクローズします(3)。
リスト3●CD-ROMドライブに挿入された音楽CDの情報をCDDBから取得するイベント・ハンドラ
 CDメディアの情報を取得できたら,CDDBControlClassクラスのLookupMediaByTocで一致するメディアがあるかどうかを検索します(4)。一致するCD情報があれば,GetMatchedDiscInfoメソッドでCDの情報が格納されているCddbDiscを取得します(5)。CddbDiscにはトラック情報のコレクションであるTracksプロパティがあるので,繰り返し処理でCddbTrackの情報を取得できます。
 コードを入力したらアプリケーションを実行しボタンをクリックしてみてください。図15のようにCDの情報が表示できたと思います。これでCDDBサービスを利用できることが確認できました。
図15●リスト3を実行してCD情報を取得したところ。デスクトップの左上にGracenoteのロゴが表示される
[画像のクリックで拡大表示]

データベースとテーブルの作成 では,いよいよ本番の楽曲管理データベース・アプリケーションを作りましょう。最初にCD情報を格納するデータベース・ファイルを作成します。
 まず新しくプロジェクトを作成し,データソース・ウィンドウを開きます。続いて「新しい接続の追加」でウィザードを起動し「接続の追加」ダイアログでデータベース・ファイル名(ここではMyCDDBとします)を入力します。ここで入力したファイルは,標準ではマイドキュメント・フォルダに格納されます。図16のダイアログでは,「はい」をクリックすると作成したデータベース・ファイルがプロジェクト配下にコピーされます。「いいえ」をクリックするとファイルはそのままで,ウィザードはその場所までの接続文字を作成し,プロジェクトに反映します。前者は,データベースを占有する場合などに選択し,後者は他のプログラムとデータベースを共有する場合に選択します。ここでは後者を選択します。
図16●データベース・ファイルをプロジェクトにコピーするかどうかを確認するダイアログ
 ウィザードを抜けると,データソース・ウィンドウにテーブルのない空のデータセットが出来ているのが確認できます。これにテーブルを追加しましょう。表示メニューでデータベース・エクスプローラを表示します。先ほど作成したMyCDDBデータベースが登録されているので,テーブルノードを右クリックして「新しいテーブルの追加」を選択します。すると中央にテーブル・デザイナが表示されるので,CD情報を格納するテーブルの定義を行います(図17)。
図17●CD情報を格納するALBUMテーブルの定義
 ここでは,アルバム情報を格納するアルバム・テーブル(ALBUM)と,CDに含まれている楽曲(トラック)の情報を格納するトラック・テーブル(TRACK)の二つを作成します。ALBUMテーブルは,CDDB検索で取得するMediaIDを主キーとします。TRACKテーブルは,ALBUMテーブルのMediaIDと関連付けしたリレーションシップを作成します。それぞれ,保存ボタン(またはCTRL+SHIFT+S)でデータベース・ファイルに保存してください。そしてもう一度データソース・ウィンドウを開き,MyCDDBノードを右クリックして「ウィザードでDataSetを構成」を選択します。ダイアログで定義通りにテーブルが作成されていることを確認したら,二つのテーブルを選択してウィザードを終了します。すると,データソース・ウィンドウに二つのテーブルが追加されます。
 続いて,テーブルにデータを登録します。ここではDataSetに編集を加えてその情報をデータベースに反映する方法で行います。まず,データソースのALBUMノードとTRACKノードをフォームにドラッグアンドドロップして,図18のようなフォームを作成します。
図18●作成するアプリケーションのユーザー・インタフェース
 ボタン・コントロールには,先ほどのCDDBアクセス・テストを参考にして,CD情報を取得する処理を記述します。CD情報がすでにデータベースに格納されている場合には同じものを登録する必要がないので,BindingSource.Findメソッドを使ってMediaIDのチェックを行います。Findメソッドの最初の引数には検索対象のテーブルのカラム名を,二番目の引数には検索のキーを指定します。検索の結果,すでに登録されている場合は,レコードのカレント位置を登録済みの情報に移動します。
 未登録だった場合は,それぞれのテーブルに格納するレコードを作成します(リスト4)。DataSetのテーブルから型付きのロウ(Row)を作成し,CDDB情報から取得した情報をそれに格納してALBUMテーブルに追加します。同様に,トラック情報も型付きのロウを作成してTRACKテーブルに格納します。CD情報の格納が完了したら,ALBUMテーブル用のBindingSourceのMoveLastメソッドを使って最後に追加された情報に移動します。

//データセットからRowオブジェクトを作成
MyCDDBDataSet.ALBUMRow aRow =
myCDDBDataSet.ALBUM.NewALBUMRow();

//Rowオブジェクトに情報を格納
aRow.MediaID = disc.MediaId;
aRow.TITLE = disc.Title;
aRow.ArtistFULLNAME = disc.ArtistFullName.Name;
aRow.ArtistNAME = disc.Artist;
aRow.GENRE = genList.GetGenreName(disc.GenreId);
aRow.NOTE = disc.Notes;

//ALBUMテーブルにRowを格納
myCDDBDataSet.ALBUM.Rows.Add(aRow);

for (int i = 1;i < disc.Tracks.Count;i++)
{
CddbTrack track = (CddbTrack)disc.Tracks;

//データセットからRowオブジェクトを作成
MyCDDBDataSet.TRACKRow tRow = myCDDBDataSet.
  TRACK.NewTRACKRow();

//Rowオブジェクトに情報を格納
tRow.MediaID = disc.MediaId;
tRow.TRACKNO = i;
tRow.TITLE = track.Title;

//TRACKテーブルにRowを格納
myCDDBDataSet.TRACK.Rows.Add(tRow);
}

//最後に追加された情報に移動して表示
aLBUMBindingSource.MoveLast();
リスト4●Rowオブジェクトを作成して,テーブルに格納する処理(ソースコードの一部)
 ここでいったんプログラムを実行してみましょう。CD-ROMドライブに音楽CDを挿入した後に「CD情報の取得」ボタンをクリックしてCDDBサービスから情報を取得してみてください。うまく情報が表示されたでしょうか。しかし,そのままアプリケーションを終了すると,せっかく取得したのにCD情報はデータベースに反映されません。なぜなら,データ操作が行われたのはメモリー上のDataSetだからです。実際のデータベースに情報を登録・反映するには,フォームの上にあるBindingNavigatorのフロッピ・ボタン(セーブボタン)をクリックする必要があります。このボタンのイベント・ハンドラは,TableAdapterのUpdateメソッドを呼び出してメモリー上のテーブルの内容をデータベースに書き戻すというもので,VS2005が自動的に作成してくれます。ただし,ウィザードが追加したコードは,ALBUMテーブルを更新するコードだけなので,TRACKテーブル登録用のメソッド呼び出しを追加する必要があります(リスト5)。

private void bindingNavigatorSaveItem_Click_1(
object sender, EventArgs e)
{
if (this.Validate())
{
  //VS 2005のウィザードが追加したコード
  this.aLBUMBindingSource.EndEdit();
  this.aLBUMTableAdapter.Update(this.myCDDBDataSet.ALBUM);

  //トラック情報反映のために次の二行を追加
  tRACKBindingSource.EndEdit();
  tRACKTableAdapter.Update(myCDDBDataSet.TRACK);
}
else
{
  // ~ 略~
}
}
リスト5●bindingNavigatorのSaveItemイベント・ハンドラに,TRACKテーブル登録用のメソッド呼び出しを追加する
TableAdapterで検索機能をカスタマイズする ここまでで,音楽情報を登録するアプリケーションは完成です。でも,データを登録したら,今度はデータの検索機能が欲しくなりますよね。最後に,皆さんが自分で今回のプログラムを機能拡張できるように,検索機能を作るためのポイントを紹介しておきましょう。
 例えば,ALBUMテーブルに登録されているアーティスト名を元にデータ(楽曲)を検索したいとします。一番簡単な方法は,検索用フォームを別に用意して,アーティスト名をドロップダウン・リストで選択すると,グリッドにそのアーティストの楽曲一覧を表示することでしょう。
 それには,まずデータソース・ウィンドウでアーティスト名をコンボボックス形式に,ALBUMタイトルをラベル形式に設定してからフォームにドロップし,続いてトラック情報をグリッド形式でフォームに追加します。さらにプロパティ・ウィンドウで,コンボボックスのDropDownStyleをDropDownListに変更し,DataSourceプロパティにALBUMテーブル用のBindingSourceを,DisplayMemberプロパティにArtistNAMEカラムを指定します。これでアーティスト別の楽曲一覧を表示することができます(図19)。
図19●アーティスト名から楽曲一覧を表示するアプリケーション
 しかし,この方法には少し問題があります。同じアーティストのCDが複数枚登録されていると,ドロップダウンリストに同名のアーティストが複数並ぶことになります。一人のアーティストの楽曲を全部表示したい場合にはこのままでは使えません。
 このようなときにこそ,TableAdapterクラスを活用します。アーティスト名を重複なしに取得し,そしてアーティスト名に一致するすべてのトラック情報を取得するSQL文を作成すればいいのです。
 データセット・デザイナを開き,ツールボックスから「TableAdapter」をデザイナにドロップします。するとウィザードが起動するので,「データ接続の選択」画面で,現在使っているデータベースへの接続文字列を指定します。そうすると,今から作るTableAdapterも同じデータベースを参照することができます。新しいTableAdapterには,アーティスト名が同じすべてのトラック情報を取得するSQL文(リスト6(a))を登録します。SQL文の作成を手助けしてくれるクエリビルダを使って,SQL文の作成と確認テストを行います。ウィザードが終了すると,デザイナに新しく追加したTableAdapterとそのSQLが返すメモリー上のテーブルが出来ています。またデータソース・ウィンドウにも新しいデータテーブルが追加されているのを確認できます(図20)。同様にして,ALBUMテーブルからアーティスト名を重複なしに取得するSQL文(リスト6(b))を登録したTableAdapterも作成してください。
リスト6●(a)アーティスト名が一致するすべてのトラック情報を取得するSQL文と(b)アーティスト名を重複なしに取得するSQL文

図20●TableAdapterを作成したことにより,二つのテーブルのほかに,新しく作成したメモリー上のテーブルが作られる
 ここまでできたら,検索用フォームを作成し直しましょう。データソース・ウィンドウから,新しく作成したテーブル(重複なしのアーティスト名)をコンボボックス形式でドロップします。次に,もう一つのテーブル(アーティストの全トラック情報)をグリッド形式でフォームにドロップします。このときBindingNavigatorが追加されますが,これは削除してください。コンボボックスのDropDownStyleプロパティをDropDownListに変更し,DataBindingsのSelectedItem,SelectedValue,Textプロパティの設定部分を「(なし)」に変更します。最後に,コンボボックスのSelectedValueChangedイベント・ハンドラに,変更されたアーティスト名を取得して,新しく作成したTableAdapterのFillByArtistNameメソッドを使ってデータベースから必要なデータを取得するコード(リストは省略)を記述します。
 アプリケーションを実行し,ドロップダウンリストにアーティスト名の重複が無くなっているのを確認してください。アーティスト名を選択すると,すべての楽曲がグリッドに表示されるはずです。
☆          ☆          ☆
 ここまで見てきたように,VS2005でのデータベース・プログラミングは,ほとんどの作業をIDE上でのGUI操作で行えます。また,SQL文をプログラム中に記述することでプログラム全体の可読性が低下してしまうことを,カスタマイズ可能なデータベース操作専用のクラス(TableAdapter)を作成することで回避するように考えられています。
 つまり,VS2005を使ったデータベース・アプリケーションの開発でもっとも重要なのは「TableAdapterをいかにうまく使いこなすか」ということと,「各種のウィザードが自動生成したコードの内容をきちんと把握すること」なのです。この二つのポイントを理解できれば,これまで以上に効率のいいデータベース・プログラミングができると思います。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-28 10:59:10 | 显示全部楼层
データベースにまつわる怖い話


怖い話 その1 終わらないバッチ処理
インテグレータ勤務 Aさん お正月気分も抜けてきた冬のある日,普段と同じように仕事をしていると,突然部長から呼び出された。あるプロジェクトでシステムの稼働開始を1カ月後に控えているのだが,開発が遅れているので支援してほしいと言うことだった。
 そのシステムは製造業のある中堅企業向けで,全国の売り上げを集計して販売分析を行うという普通によくあるものであった。3週間前にプログラミング作業に着手し,予定ではすでにシステム・テストに入っているはずなのに,まだ修正を続けているらしい。
「やはりオープン系RDBは使えない?」 聞いてみると,夜間の6時間で終わらなければならないバッチ処理が,12時間たっても終わらないのだと言う。ちなみに,そのバッチ処理は,Oracleのストアドプロシジャを使って記述している。売り上げの元データの件数は100万レコード以上。データ量が多いので集計処理に時間がかかっているということだった。しかし,サーバーは十分なスペックのものを用意しているはずである。元々の見込みでは2,3時間で終わるはずの処理が12時間たっても終わらないというのはどうもおかしい。
 プロジェクトのメンバーは,汎用機(メインフレーム)アプリケーションの開発の経験はあるが,オープン系のRDBを使った開発は今回が初めてだという。当初から,メンバーの中ではオープン系のRDBは不安定で遅いという印象があったらしい。このような事態に直面するに至って「やはりオープン系のRDBは使えない」という雰囲気がプロジェクトに蔓延していた。
 バッチ処理の流れを聞いてみると,年月日・店舗・商品ごとの売り上げデータを集計して,(1)商品ごとの月別売り上げ,(2)地域別に見た商品ごとの月別売り上げ,(3)商品分類ごとの月別売り上げ,(4)地域別に見た商品分類ごとの月別売り上げを出力するというものであった(図1)。システムの仕様として,特に難しいところはない。
図1●売り上げ集計処理の流れ
 テーブル設計を見せてもらうと図2のようであった(詳細は省略してある)。こちらも別に難しいところはないが,ちょっと不安になった。テーブル間の関連,つまり外部キーなどが一切設定されていないのである。図2のようなテーブルであれば普通,店舗テーブル,商品テーブルと売り上げテーブルが「1対多」の関係にあるのは明らかである。しかし,これらのテーブルには何の関連も設定されていない。
図2●売り上げ集計に利用するテーブルの設計
 実際のバッチ処理のプログラムを見せてもらって,がく然としてしまった。集計処理のプログラムで,ORDERBY句を付けてSELECTすることによってソートした結果セット(カーソル)を作成した後,カーソルの各行に対してループをまわして加算することで合計を求めていたのである(図3)。
図3●売り上げ集計処理のロジック
[画像のクリックで拡大表示]
 しかし,そのような集計処理を行わなくても,集合関数(SUM)とGROUP BY句を使えば,グループごとの集計を一つのSQL文で計算させられるのである。そのことを担当者に告げると「えっ,SQLってそんなこともできるんですか!」と驚く始末。
 ストアドプロシジャでカーソルを使うような“凝った”プログラムを書いておきながら,SQLの基本的な関数を知らなかったのである。集合関数を使って処理を書き直すと,数十行あったプログラムが数行で済んでしまった。
 しかし,プログラムを実行してみると,今度はまったく応答が返ってこなくなってしまった。実は,GROUPBY句を付けた処理は,データベース管理システム(DBMS)内部でソートを行っている。今回対象にしたテーブルは100万レコードもあるため,全件を対象としたSQL文を実行すると,膨大な計算量になってしまう。加えて,図2のテーブル設計では,検索対象のデータ項目にインデックスが付けられていない。
 そこで,商品コードにインデックスを作成し,商品コードごとにデータを区切って集計するSQL文に書き替えた。その結果,処理時間は大幅に短縮され,半日たっても終わらなかった処理が1時間半で終了するようになった。
 この件は,バッチ処理のSQL文の書き方やインデックスが作成されていないのが直接の原因であった。だが,もっと根本的な原因は,RDBの特性を知らずに設計してしまったことにある。全体として,汎用機アプリケーションでファイルを順次処理するようなイメージで設計してしまったためであろう。不必要な中間テーブルが多数あり,無駄なバッチ処理が数多くあった。

怖い話  その2 データベースはゆっくりと死んでゆく
(株)システムインテグレータ 梅田 弘之さん 日本中がサッカーのワールドカップで盛り上がった2002年のことです。社員が数人というベンチャー企業が弊社(システムインテグレータ)のツール「SI Web Shopping」を使ってECサイトを再構築しました。これはそのときの話です。
 この企業は,その数年前にサッカーの各国代表チームのユニフォーム(レプリカ)を販売するサイトを立ち上げました。そしてワールドカップ前年(2001年)からサイトへのアクセスが少しずつ増え始め,売り上げも伸びてきました。当然のことですが,アクセスが増えるとサイトのパフォーマンスが悪化します。このサイトは元々その企業の“手作り”でした。これからワールドカップ需要が見込めるということもあり,パフォーマンス向上を目指したサイト再構築をうちが担当することになりました。
無償ソフトを活用してECサイトを再構築 とはいえ,あまりお金をかけられません。そこで1CPUの1サーバー(Windows2000)を新たに導入した以外は,Webサーバー・ソフトはIIS(Internet InformationServices)とデータベース・ソフトはMSDE(Microsoft SQL Server DesktopEngine)というに,マイクロソフトが無償で提供するソフトを活用することにしました。MSDEをWebアプリケーションで使う場合には「同時ユーザー数は25以下」とされていることは知っていましたが,ある程度の高負荷でもちゃんと動くことをこれまでの経験から確認していたので採用することにしたのです。
 サイトを再構築した後,ワールドカップの3~4カ月前ぐらいからアクセスが急増し,売り上げは一気に増えました。しかし同時に,当初の想定を大きく上回るアクセス量になったためパフォーマンスの問題が発生しました。ユーザーが買い物をしている途中にシステムがハングアップする現象がしばしば起こるようになったのです。ログを見ると,毎日,21時ぐらいからアクセスが増え,23時ごろにピーク(約500セッションの同時アクセス)を迎え,それから午前1時ぐらいまでの間に何度かハングアップするという状態でした。
 対策としてまず,データベースのインデックスをうまく活用できているか,非効率なSQL文はないかといったプログラムの見直しを実行。さらに,Webサーバー・ソフトとデータベース・ソフトを別々のマシンで実行するという2サーバー構成に変更しました。データベース・サーバーは新たに導入した2CPUのマシンです。
 念のためにこの構成で,マイクロソフトのテスト・ツール「MS Web ApplicationStress」を使って負荷テストをして動作を確認してみました。200セッションぐらいの同時アクセスであれば,問題なく動作します。500セッションぐらいの負荷をかけると,ややキツくなるようでしたが,それでもパフォーマンスの顕著な落ち込みは見られませんでした。
ピークを乗り切った数カ月後に問題が表出 実際,この対策を施すことで,サイトのパフォーマンスは一気に改善しました。それでもワールドカップ期間中は,このサイトのことが気になって,(ほかの人たちとは別の意味で)はらはらドキドキの日々でした。真夜中にアクセスのピークを迎えるため,毎晩遅くまでサーバーの監視する必要がありました。日本チームが勝ち抜くたびに,ユニフォームを購入するお客さんがサイトに殺到するので,そろそろ負けてくれと願ったときもありましたが,ともかく一番の稼ぎ時であるワールドカップの期間を無事乗り切れました。
 ところが,ワールドカップが終わって数カ月経ったころ,またパフォーマンスが悪くなったという連絡がありました。アクセス数がワールドカップのときより増えているわけではありません。アクセスのピークを乗り切ったはずなのになぜ,といろいろと調べたのですが原因はよくわかりません。サーバーのメモリーを増やすなどの対策を施しましたが,状況は同じです。悩んだ末,やはりデータベース側に問題があるのだろうと思い,試しにMSDEをSQLServerに変えてみました。すると,あっさり問題が解決してしまいました。
 「MSDEを本番環境で使う方がおかしい」と思われるかもしれません。しかし,負荷テストで500セッションまで動作確認し,実際に問題なく動作していたので,原因がMSDEにあるとはなかなか気づかなかったのです。その後,「MSDEは高負荷が長期間続くと性能が劣化するらしい」ことがわかりました。今回の一連の出来事から導き出される教訓があるとすれば,(1)ECサイトはきちんと需要予測してそれに耐えるマシン構成にする,(2)ソフトウエアはメーカーの推奨通りに使う,ということでしょうか(笑)。
怖い話  その3 存在する商品のデータがマスターに無い!
真野 正さん これは,あるアパレル系会社で実際にあった話に基づいています。X社では,自社のプライベート・ブランド商品を開発・販売して,独自の店舗展開で売り上げを順調に伸ばしていました。各店舗のPOSデータは本部コンピュータに送信されて売り上げ管理システムで日別の店舗別売り上げが把握できる仕組みになっています。一方,これとは別の管理会計システムがあり,こちらでは店舗別・商品別の原価計算,利益分析を行い,販売戦略や新商品開発に活用しています。
 ある日(8月10日)のことです。担当者が管理会計システムで商品別の原価計算をしていたところ,ある商品で「商品マスターに存在しません」というエラー・メッセージが表示されて,システムがストップしてしまったのです。早速翌日(8月11日),情報システム部門の管理会計システム担当のAさんとマスター管理システム担当のBさんが,原因を探るべく活動を開始しました。
 ところが,問題の商品のデータは確かに商品マスターに存在しています。二人は一瞬狐につままれたような面持ちになりました。しばらくしてBさんが,「あ,そうだ。商品マスターは,商品履歴データを一緒に持っていない商品については存在しないと見なしているんだ」と気づきました。商品履歴データとは,仕入価格,販売価格,デザイン変更など商品に関する過去の情報です。
 そこで調べてみると,商品履歴データもちゃんと存在します。ますますわけがわからなくなったのですが,Aさんはふと思いつきました。「ところで,商品履歴のデータはいつ作られるの?」。Bさんが「商品を実際に店舗で売り始める取り扱い開始日以降だよ」と答えると,Aさんが「えーと,この商品は,取り扱い開始日が今日(8月11日)だよね」と言ったところで“謎”は解明しました。
業務内容に合わない仕様が根本原因 もう,おわかりですね。8月10日に処理した時点では商品履歴データが存在しなかったため,商品そのものがマスターに無いとされてしまったのです。こうした仕様になっているのは,売り上げ管理システムが作られてから会計管理システムが作られたという歴史に関係していました。
 売り上げ管理システムは取り扱い開始後の商品を対象としますから,履歴データの有無をチェックして履歴がある商品だけ扱うという仕様になっていたのです。一方,原価管理などを行う管理会計システムは,履歴データは必要としません。それにもかかわらず,売り上げ管理システムの仕様をそのまま適用してしまったところに,今回のトラブルの原因がありました。業務内容に合わないシステム仕様になっていたのです。
 X社ではとりあえず,「マスター管理システムから管理会計システムに転送する際か,あるいは,データを受け取った管理会計システム側で,ある基準日以降であれば,取り扱い開始日以前でも履歴データを付加するようにする」ことで,トラブルが再発しないようにしました。しかし,このままでは,売り上げ管理と管理会計システムでマスターの不整合が発生し,また違ったトラブルを招く可能性があります。そこで,これを契機に,システムごとに重複して配備されているマスターの“源泉からの流れ”を明確にしようという調査プロジェクトがスタートしました。
 いかがでしたでしょうか,怖かったですか? 「全然怖くない!」。しかし,皆さんの会社でも少なからず,似たようなことが行われている可能性は大いにあります。問題が表面化していないだけかもしれません。

      怖い話  その4 ソフトのバグにミスが追い討ち データベースが修復不能に
フリープログラマ 中條 達雄さん ワイン通は,過去の出来事がいつのことであったのか,その年のブドウの出来不出来を手がかりに思い出すという。コンピュータ技術者はソフトウエアや規格書のバージョンを手がかりにする。例えば「あれはちょうどWindows3.1のリリース直後」とか,「まだRFCが800ぐらいしかなかったころ」といった具合である。これからお話しするのは,まだPostgreSQLのバージョンが6.xのころ(1999年)の出来事である。はるか昔のことなので,細部に記憶違いがあるかもしれないがご容赦いただきたい。
 当時,私は一般向けの会員制のWebサービスの開発を請け負っていた。一般向けの有償サービスで,24時間稼働,期待される会員の数は数万名単位のシステムを,すべて一人で開発するという経験は初めてであった。数カ月の開発期間を経て無事サービス・インを迎えた。滝のように流れていくアクセス・ログを眺めながら,ああこれでひと仕事終わったと安堵し,そのまま解放感に任せてしばらく遊び暮らすつもりでいた。
 ところが数日ほどたってトラブル発生の連絡が来た。アクセスが予想以上に集中して,エラーが頻発して使いものにならなくなるということだ。問題があるからといって,「調査しますのでしばらくサイトを閉鎖します」というわけにはいかない。すでに月会費を払って利用しているユーザーがいるのだ。そこで対処療法を考えた。一つは,Apacheのパラメータを調整して,CGIの同時処理数を減らすこと。同時処理数を超えたアクセスは待たされることになるが,そのぶんサーバーの負荷は下がるから,エラーの頻度は下がる。エラーになるよりは待たされる方がまだましだろうという理屈だ。もう一つは,データベースの負荷分散に備えて採用した接続ロジックを変更して,データベースへの接続回数を減らすことである。
「魔が差した」としか言いようがない これで何とか落ち着いたものの,当然のことながら顧客からは根本的な解決を迫られる。やはりデータベースの設計から多少変更しなくてはならない。休む間もなく性能改善の作業にかかった。
 そこで,データベースの設計に手を加え,プログラムを修正。手元の開発環境で動作を確認し,現行のデータベースからの移行手順をおさらいした。システムを更新する準備ができたことを顧客に連絡する。システムを停止するのは最長3時間までしか許されていない。まず,サイトのコンテンツをメンテナンス中の表示に切り替える。そして,データベースのバックアップを取って,プログラムを差し替えてからデータベースの移行に取りかかる。
 簡単に言えば,移行元のテーブルから,SQL文を使って新しいテーブルを生成した後,テーブルの名前を変えて元のテーブルと差し替えるという作業である。作業自体は順調に進んだ。あとは顧客に連絡して最終確認をしてもらい,問題がなければ無事サービス再開するばかりであった。しかし「魔が差した」とでも言うのだろうか。ついでにもう一つ作業をしておこうと考えた。
 PostgreSQLでは,レコードの削除や更新で不要になった領域を回収して再利用する処理(ガーベジ・コレクション)を手作業で起動する仕組みになっている。バキュームと呼ばれる作業である。サービス再開の前にバキュームをしておけば,多少はパフォーマンス改善につながるかもしれない。そう思って,コンソールに向かってvacuumとタイプし,処理が終わるのを待った。
 処理が終わったようなので念のためSELECT文をタイプしてみた。と,なんとデータベースがエラーを返してくるではないか。一瞬背中がひやっとする。やばい。そんなバカな。念のため,もう一度ほかのテーブルをSELECTしてみる。やはりエラーになる。原因はよくわからないのだが,データベースが壊れてしまったらしい。バックアップからリストアして移行作業をやり直すしかない。
 この時点で残り時間は30分。しかたがないので技術担当者に事情を連絡し,メンテナンス時間を延長してもらう。データベース・ファイルを再生成し,バックアップからリストアをして…。ところがリストアしようと,エラーになってしまう。バックアップがリストアできない?これはまずい…,でも一体どうして?
 顔面蒼白になって客先の技術担当者に連絡を取る。悪いことはできるだけ早く伝えなくてはならない。「実はこういう問題が発生しまして。ユーザーのIDだけは復元できるのですが,ユーザー情報は日本語コードを使っているので,ほぼ復元できないと思われます」。しばし沈黙が続く。相手の頭の中も,おそらく真っ白になっているに違いない。
テキスト形式でバックアップするなんて 間もなく電話の向こうから返答があった。「仕方ありません。トップページに,事故があったことのお詫びと,ユーザー情報が消えてしまったお客様に再入力を促すメッセージを表示しましょう。原稿は早急にこちらで考えます」。
 電話を置いて,落ち込んでいてもしようがないので作業に戻る。バックアップ・ファイルを調べているうちに,リストアできない理由がわかってきた。PostgreSQLに格納する文字コードのエンコーディングとして「MULE_INTERNAL」を指定してしまっていたのだ。一方,CGIプログラムはシフトJISコードのままデータを格納していた。ちょっとややこしくなるが要するに,「シフトJISコードをMULE_INTERNALコードであるかのように格納していたデータを,バックアップするときにMULE_INTERNALコードからシフトJIS変換してしまった。そのため,日本語文字コードが復元できなくなった」ということである。
 環境設定ミス+オペレーション・ミスの複合エラーによるものと言われてしまえばそれまでである。しかし,「なぜデータベースのバックアップをテキスト形式で,しかもクライアント側のエンコーディングを意識してコード変換して行う仕様になっているのか,データベースの内部コードのままバックアップすればいいじゃないか」という憤りは,プログラマの方なら少しはご理解いただけると思う。
 理由がわかったので,Perlでエンコーディングの逆変換を行うプログラムを作成して復元を試みる。しかし,どうしてもうまくいかない文字がある。バックアップ・ファイルを16進ダンプして調査したいが,そろそろタイムアウトである。復元できなかったレコード数をカウントし,トップページにお詫びのメッセージを入れてから担当者に連絡する。動作確認についてはあっさりOKが出た。サービス再開である。深夜に移行作業の準備から始めて6時間近くたっていて,すでに外はすっかり明るくなっていた。
 まだやることは残っている。4,5時間睡眠を取ったところで障害報告のレポートをまとめて提出し,疑問点の解決と今後の対応を図るための調査を始める。バキュームの件は,どうやらPostgreSQLのバグである。バックアップについては,環境変数を明示的に指定してデータベース側のデフォルト・エンコーディングでバックアップする。あるいは,もっとも確実な方法としてサーバー側で行うバイナリ・ダンプの機能を使う。もしくは,データベースをいったん停止してデータベース・ファイルごとバックアップしておく,など。
 PostgreSQLのバグが関係しているとはいえ,私にも慎重さの点で反省すべき点がいろいろとあって後味が悪い。しかし,不幸中の幸いと言うのだろうか。この事故があってから,PostgreSQLで何かトラブルがあると私に質問が来るようになった。顧客の厚意で,PostgreSQLを有償サポートしている企業のサポート窓口のアカウントを提供してもらったり,PostgreSQLのコア・メンバーが来日したときの講演会に出席させてもらったりといろいろと勉強できた。技術者と顧客はきっと,ある程度のリスクを共有しながら,技術の発展と普及に貢献しているのだろう。次々と新しい技術が登場する中,私たち技術者は日々努力しているが,それでも(私の場合は)2~3年に一度はこうした前代未聞のトラブルに巻き込まれることは避けられない。しかし,トラブルを避けて通りたいと考えるようになったら,そこで技術者生命はおしまいなのではないかと思う。
怖い話  その5 負荷テストの結果にユーザーが激怒
インテグレータ勤務 Bさん ある企業の業務系Webシステムの開発プロジェクトで,ユーザー・テストを行うことになった。システム・テストが完了し,リリースまで2カ月という時点である。開発は決して余裕のあるスケジュールではなかったが,何とか予定通りに顧客の要望通りの機能が出来上がっていた。
 システム・テストでは,細かな仕様漏れがいくつか見つかったが,大きな変更もなくユーザー・テストにこぎつけることができた。しかし,開発チームには一抹の不安があった。機能の作り込みは何とか間に合わせたが,負荷テストをまったくしていなかったのだ。ユーザー・テストでは,本番に近い人数で同時に利用して応答時間を計るという項目もある。心配ではあったが,開発中では応答時間も気になるほどではなかったため,人数が増えても多少遅くなる程度だろうと思っていた。
 しかし,いざ負荷テストを始めてみると,1人のときに2,3秒だった応答時間が5人だと20秒,10人では50秒近くかかってしまったのである。ユーザーは激怒して,すぐに対処するように求めてきた。あわてて原因を調べると,どうやらアプリケーション・サーバーのCPUがすぐに100%になってしまうことがわかった。通常5人や10人の同時ユーザーでCPUの処理が足りなくなることは少ない。何か非効率な処理があるはずだ。
再利用性を意識するあまり効率が悪いDB接続に 実は,このWebシステムは,今後パッケージ化することを計画していた。そのため,アプリケーションの柔軟な再利用ができるように,かなり細かくモジュール化されていた。今回の問題の原因は,データ・アクセスのモジュールにあった。モジュール間でのデータベース(DB)コネクション(接続)の共有やコネクション・プール(コネクションを貯めておいて使いまわす仕組み)の利用が行われていなかったため,DB接続が非常に非効率になっていたのだ。リクエストが来るたびにDB接続を保持したモジュールのインスタンスが作られる仕様になっていたので,人数が増えるとすぐにサーバーのリソースが不足してしまう。
 原因はわかったものの,改善にはかなりの工数を要した。DB接続は,アプリケーション全体で利用されているため,システムの全面的な見直しが必要になったからだ。結果として,システムのリリースを遅らせざるを得なかった。
 DB接続以外でもリソースに関する問題が起こる。よくあるのはDBサーバーのキャッシュの影響である。アクセスを続けていると知らない間にディスク・キャッシュが増えてメモリーを圧迫することがある。そのほか,ファイルの読み書きを監視する常駐型ウイルス対策ソフトも,DBサーバーには影響が大きい。ウイルス・スキャンを停止するだけで,パフォーマンスが2,3割も向上することがある。しかし,サーバーのウイルス・スキャンは安易に停止できない。個人情報漏洩などの問題が大きくなった昨今,データは強固に守らなければならない。DBのデータ・ファイルだけスキャンの対象からはずすなどの対処が必要となるだろう。

怖い話  その6 まずい設計に事前に気づき危うく難を逃れる
インテグレータ勤務 Cさん ある大学で教務管理システムを刷新するプロジェクトが始まった。プラットフォームは,当時の最先端であるJ2EEサーバー。プロジェクトにおける私の役割は,J2EE開発の標準化を行うことである。
 プロジェクトは,設計を行う「業務チーム」と開発を行う「Javaチーム」の二手に分かれて進んだ。Javaチームは,Webの画面やJava開発の標準化をしながら先行開発を行う。データベース設計を担当する業務チームのメンバーはRDBのシステム開発は初めてという人が多かった。しかし,設計に先立ってRDBについて勉強しており,主キーやインデックスなどの基本的概念は理解しているようだった。
関連が切れたテーブルと多すぎる主キー やがてJavaチームは,業務チームから渡されたDB設計と詳細設計に基づいて,Webアプリケーションの開発に着手した。しかし,渡されたDB設計を見て首をひねった。テーブルの関連が切れている個所がある(図4(a))。ちょっと考えると,学生テーブルと履修テーブル,科目テーブルと履修テーブルの間にはそれぞれ関連がありそうだ。しかし,設計を見てみると,科目テーブルと履修テーブルの間に関連がない。
図4●(a)業務グループが最初に持ってきたテーブル設計。(b)代替キーを使って設計を見直した結果
[画像のクリックで拡大表示]
 担当者に聞くと「業務を考えて設計したらこのようになった」と言って理由を説明してくれた。科目テーブルの主キーは,「年度」「学期」「科目コード」「科目区分」「学生区分」の五つ。一方,履修テーブルの主キーは,「学生番号」「学期」「科目コード」「科目区分」である。科目コードは年度や学期ごとに決めるため,年度,学期,科目コード,科目区分,学生区分を決めないと科目名が一意に決まらない。また,履修テーブルでは,学生番号から学生区分がわかるし,対象とする1年ぶんのデータしか持たないので,年度は必要ない。その結果,科目と履修の間に関連がなくなったらしい。
 理由はわかったものの図4(a)のテーブル設計のままシステムを実装するわけにはいかない。この設計には問題となる点がいくつもあるからである。
 一つは,画面や帳票の表示処理が難しくなると言うことだ。例えば,履修の一覧を出す場合,年度や学生区分を保持して履修テーブルを検索しなければならない。科目テーブルと履修テーブルの間に関連が付けられていないので,JOIN処理でテーブルを結合して表示させることができない。
 主キーの項目が多いのも問題である。主キーにはインデックスが作成される。主キーの項目が多いと,インデックスのサイズが大きくなり,ディスク・アクセスのオーバーヘッドが起こったり,格納効率が悪くなったりする。加えて,テーブルの主キーが多いと,更新処理のときにSQLのWHERE句に多くの項目を指定する必要がある。つまり,プログラムのなかで多くの項目を受け渡ししないと処理が行えないのである。一般的に,主キーは3項目以下にするべきだ。
 こうした問題は,「代替キー」を追加することで解決することが多い。代替キーは,業務的な意味を持つ項目ではなく,単に行を一意に特定するために使われるキーである。代替キーをうまく導入すれば,主キーの項目数を減らすことができるし,関連も設定できるようになる。
 そこで科目テーブルに「科目ID」という代替キーを設定し,テーブルを設計し直した(図4(b))。このプロジェクトでは,初期でDB設計を見直すことで,期日に遅れないように開発が完了した。もし図4(a)の設計のままプログラミング作業に入っていたら,プログラミングの工数が膨らみ,稼働開始が遅れていた可能性が高い。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注~册

本版积分规则

小黑屋|手机版|咖啡日语

GMT+8, 2025-1-26 06:05

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表