レベル: 中級 John Chun, DB2 Advanced Support Specialist, IBM Christine Law, DB2 Advanced Support Specialist, IBM Salvador Ledezma, Staff Software Engineer, IBM Alex Pitigoi, Advisory Software Engineer, IBM
2007年 6月 07日 XML は、今日の Web で最もよく使用されるデータ交換フォーマットの 1 つとして数えられています。DB2® の pureXML™ サポートを Ruby on Rails に用意された XML API (REXML) の解析および生成と組み合わせると、Web アプリケーション開発のための強力な組み合わせになります。DB2 データ・サーバーにおけるネイティブ XML サポートは、そのハイブリッド・データベース・エンジンのコンパイラーとパーサーを利用して、リレーショナル・データと併せて半構造化された階層構造の XML 文書を保管するという柔軟性をもたらし、SQL と XQuery の両方に対応します。連載「DB2 と Ruby on Rails」の第 2 回では、第 1 回の記事で使用したサンプル・アプリケーション Team Room を実例に用いて pureXML を活用する方法を説明します。
はじめに
連載「DB2 と Ruby on Rails」の第 1 回では、Ruby on Rails と DB2 を使って Team Room をビルドしました。これは、登録されたメンバーが各種のテキスト文書、画像ファイル、そして
XML 文書を共有できるようにするアプリケーションです。このアプリケーションを使って、増え続ける共有文書を管理するために文書をカテゴリー別に分類し、それからサブスクリプション機能を追加して、新しい文書が文書カテゴリーに加えられたときにそのカテゴリーをサブスクライブしているユーザーに
E メールで通知する方法を説明しました。記事の最後では、メンバーがさまざまな種類のファイルを Team Room にアップロードしてバックエンドの
DB2 データ・サーバーに保管できるようになりました。今回は、Team Room を拡張してさらに高度なユーザー機能を提供し、リソースへのアクセスを改善します。
開発を進めるための Team Room アプリケーションの更新、パート 1
ステップ 1. ユーザー管理機能を追加する
まず、ユーザー・モデルとその基礎となる表のパーシスタンスに必要な追加を行って、適切な認証を有効にするところから始めましょう。追加するのは固有のユーザー
ID ストリングと、SHA アルゴリズムを使用して擬似ランダム・シード (salt) でハッシュ化したパスワードです。ここではコントローラーと表示に多少の変更を追加して新規ユーザーの登録とセキュアなログインを可能にしていますが、このユーザー・モデルに他のユーザー属性
(アクティブであるか、ランクなど) を追加すればさらに改善することができます。サンプルの Rails プロジェクトは D:\rails\teamroom
に置かれているので、以下で参照しているすべてのパスは、D:\rails\teamroom ディレクトリー内での相対パスです。
a) ruby script/generate migration add_user_credentials_columns を実行して、USERS 表に必要な列を追加するためのマイグレーション・プロセスを開始します。
b) db/migrate/008_add_user_credentials_columns.rb ファイルを編集して必要な列を追加します (リスト 1 を参照)。
リスト 1. 008_add_user_credentials_columns.rb の編集
class AddUserCredentialsColumns < ActiveRecord::Migration
def self.up
add_column :users, :userid, :string, :limit => 8
add_column :users, :hash_passwd, :string
add_column :users, :salt, :string
end
def self.down
remove_column :users, :userid
remove_column :users, :hash_passwd
remove_column :users, :salt
end
end |
c) rake db:migrate を実行して上記の新しい列を USERS 表に追加します。
ステップ 2. サブジェクトを複数のユーザー・サブスクリプションで利用できるようにする
前回の記事で説明した Team Room アプリケーションでは、それぞれのサブジェクトは 1 つのサブスクリプションにしか属せませんでした。各サブスクリプションはユーザーがサブスクライブしたサブジェクトの集合であることを思い出してください。このため、ユーザーが特定のサブジェクトを選択すると、選択されたサブジェクトは
Team Room の残りのユーザーには利用できなくなってしまい、Team Room を非現実的なアプリケーションにしているのです。
Team Room の人気を考えると、多くのメンバーが複数のサブジェクトをサブスクライブしたいと思うはずです。ここはメンバーの要求に応えて、1
つのサブジェクトに複数のサブスクリプションを許可するしかありません。そこで、更新後の Team Room では、単一のサブジェクトを複数のサブスクリプションで利用できるようにしています。そのために実装する変更は以下のとおりです。
- 以前は、SUBSCRIPTIONS と SUBJECTS の関係は 1 対多でした。ただし、各サブジェクトが属せるのは 1 つのサブスクリプションだけです。この制限を解除するには、SUBSCRIPTIONS
と SUBJECTS との間で多対多の関係を許可する必要があります。
- データベースを確実に正規化するため (注 1 を参照)、SUBJECTS_SUBSCRIPTIONS という新しい表を作成して SUBJECTS 表と SUBSCRIPTIONS 表を結合します。この名前を見るとわかるように、中間結合表の名前には
Rails の規則を使用します。アクティブ・レコード (Active Record) は、この結合した表の名前が 2 つのターゲット表の名前をアルファベット順に連結したものであるいう前提で、2
つのターゲット表をリンクする外部キーのペアを格納します。
- 既存の関連を変更し、変更を反映するために新しい関連を SUBJECTS_SUBSCRIPTIONS 表、サブジェクト、およびサブスクリプション・モデルに追加します。
 | |
注 1: DB2 Database for Linux, UNIX, and Windows インフォメーション・センターの「正規化」セクションを参照してください。
|
|
SUBJECTS_SUBSCRIPTIONS 表に含まれる列は以下のとおりです。
表 1. SUBJECTS_SUBSCRIPTIONS 表の列とその説明
| 列名 | データ型 | 説明 |
|---|
| SUBSCRIPTION_ID | 整数 | SUBSCRIPTIONS 表の外部 ID |
|---|
| SUBJECT_ID | 整数 | SUBJECTS 表の外部 ID |
|---|
マイグレーションという方法で変更を実装するには、以下のステップに従います。
a) ruby script/generate migration create_subjects_subscriptions_table を実行します。
b) db/migrate/009_create_subjects_subscriptions_table.rb ファイルを以下のように編集します。
リスト 2. 009_create_subjects_subscriptions_table.rb の編集
class CreateSubjectsSubscriptions < ActiveRecord::Migration
def self.up
create_table :subjects_subscriptions, :id => false do |t|
t.column :subscription_id, :integer, :null => false
t.column :subject_id, :integer, :null => false
end
remove_column :subjects, :subscription_id
add_index :subjects_subscriptions, :subject_id
end
def self.down
drop_table :subjects_subscriptions
add_column :subjects, :subscription_id, :integer
end
end |
c) rake db:migrate を実行して SUBJECTS_SUBSCRIPTIONS 表を作成します。
d) /app/models/subject.rb ファイルで、既存の関連 belongs_to :subscription を新しい関連 has_and_belongs_to_many :subscriptions に置き換えます。
e) /app/models/subscription.rb ファイルで、既存の関連 has_many: subject を has_and_belongs_to_many :subjects に置き換えます。
 | |
注 2: マイグレーションを元に戻す場合は、それぞれのモデルのデータベース・オブジェクトとの関連が変更されて適切に元に戻っていることを確認してください。
|
|
ステップ 3. XML データ: 顧客情報
マーケティング・チームでは顧客の購買傾向を分析するため、匿名の顧客情報を XML 形式で収集しました。以下は、小売業者が市場調査のために収集すると考えられるデータの例です。
リスト 3. XML 文書の例
<marketinfo xmlns="http://www.ibm.com/developerworks">
<sales>
<customer>
<address>
<city>Nashville</city>
<state>TN</state>
<zip>46808</zip>
</address>
<categories>
<category type='Toys'>
<item>
<SKU>2434901</SKU>
</item>
<item>
<SKU>9043272</SKU>
</item>
</category>
<category type='Video Games'>
<item>
<SKU>1915216</SKU>
</item>
</category>
</categories>
<last_purchase>2007-05-12</last_purchase>
</customer>
</sales>
</marketinfo> |
それぞれの顧客購入情報には、米国またはカナダ国内の住所、製品カテゴリーの詳細 (商品の SKU (Stock Keeping Unit: 在庫保管単位)
番号など)、そして前回の購入日が含まれます。製品カテゴリーには以下のものがあります。
- 衣料
- 自動車
- 赤ちゃん用品
- 本
- コンピューター
- 化粧品
- 電化製品
- 園芸用品
- 家庭用品
- ジュエリー
- 映画
- 音楽
- ペット
- 医薬品
- スポーツ
- 玩具
- テレビ・ゲーム
Team Room アプリケーションを使って上記の XML データにクエリーを実行する方法については、次のセクションで説明します。
この XML 形式の顧客データには、テキスト文書形式の市場調査レポートを関連付けることができます。市場調査レポートには市場分析、あるいはデータ収集方法の詳細を含められます。
第 1 回で、収集したマーケティング・データを保管する XML 型の列を作成したことを思い出してください。DB2 は XML データを従来の
SQL データ型と同じように効率的に管理するため、内部では XML データ・モデルを論理データ・モデルとして使用するとともに、物理ストレージの基本単位としても使用します。さらに、XML
データ型が指定されたときにはこのデータ・モデルがデータベースのユーザーに公開されます。このような仕組みは、特に XML 中心の開発者にとって
XML データの管理を強力かつ柔軟に行えるようにしていますが、現時点では、従来のデータベース管理アクティビティーのタイプによっては XML 列で実行できることに制限があります。例えば、XML
の階層ストレージ構造のために、DB2 では XML 列を持つ表を物理的に再編成することができません。つまり事実上、XML 列が含まれる表では
ALTER 操作によって列を削除できないということです。この制約は今後のリリースで取り除かれる可能性がありますが、ALTER 操作は Ruby
on Rails のマイグレーションに基本的な操作なので覚えておいてください。
XML ならではの利点を生かしつつも Ruby on Rails のマイグレーションが持つ柔軟性を損なわないようにする方法は、XML データを保管する別の表を作成することです。サンプル・アプリケーションでは、この表を
XML_CONTENTS と呼ぶことにします。この表に XML 文書を保管し、他のすべての関連情報は DOCUMENTS 表に今までどおり保管します。このようにすれば、DOCUMENTS
表の列を追加または削除するときに、XML データによって影響されたり、XML_CONTENTS 表の XML データに影響したりすることがありません。
XML データ型を使用する上での現在の制約事項についての詳細は、IBM DB2 Database for Linux, UNIX, and Windows
インフォメーション・センターの「ネイティブ XML データ・ストアの制約事項」を参照してください。
XML データを保管する別の表を作成するには、以下のマイグレーションを実行します。
a) ruby script/generate migration create_xml_contents を実行します。これによって、db/migrate/010_create_xml_contents.rb ファイルが作成されます。
b) db/migrate/010_create_xml_contents.rb ファイルを以下のように編集します。
リスト 4. 010_create_xml_contents.rb の編集
class CreateXmlContents < ActiveRecord::Migration
def self.up
drop_table :documents
create_table :documents do |t|
t.column :name, :string, :null => false
t.column :size, :integer, :null => false
t.column :data, :binary, :limit => 2.megabytes
t.column :content_type, :string, :null => false
t.column :created_at, :timestamp
t.column :updated_at, :timestamp
t.column :platform, :string, :limit =>10
t.column :subject_id, :integer
t.column :user_id, :integer
end
create_table :xml_contents do |t|
t.column :name, :string
t.column :data, :xml, :null => false
t.column :document_id, :integer
end
end
def self.down
drop_table :documents
drop_table :xml_contents
create_table :documents do |t|
t.column :name, :string, :null => false
t.column :size, :integer, :null => false
t.column :data, :binary, :limit => 2.megabytes
t.column :content_type, :string, :null => false
t.column :created_at, :timestamp
t.column :updated_at, :timestamp
t.column :platform, :string, :limit =>10
t.column :subject_id, :integer
t.column :user_id, :integer
t.column :xmldata, :xml, :null => false
end
end
end |
c) rake db:migrate を実行して既存の DOCUMENTS 表を削除し、XML_CONTENTS という名前の XML データ専用の新しい表と、XML 列がない新しい
DOCUMENTS 表を作成します。
d) 新しい DOCUMENTS 表と XML_CONTENTS 表との関係を再設定します。
まず、ステップ d で生成された /app/models/xml_content.rb ファイルに関連 belongs_to :document を追加します。
次に、/app/models/document.rb ファイルに関連 has_one :xml_content を追加します。
e) アップロード機能は、以前に前の文書モデル (document.rb) に実装した内容と同様です。今ではこの文書モデルが親モデルとなり、子モデル
(xml_content) を作成するための処理を追加します。
NAME 列には元のファイル名が入力されます。
リスト 5. DOCUMENTS レコードへのファイル属性の割り当て
self.name = File.basename(doc_field.original_filename).gsub(/[^\w._-]/, '')
self.content_type = doc_field.content_type.chomp
self.size = doc_field.size
self.created_at = Time.now |
XML_CONTENTS.DATA 列には指定されたファイルが入力されます。
リスト 6. XML_CONTENTS.DATA への XML ファイル・コンテンツの割り当て
unless self.content_type.include?('text/xml')
self.data = doc_field.read
else
content = XmlContent.new
content.name = self.name
content.data = doc_field.read
self.xml_content = content
end |
最終的な /app/models/document.rb はリスト 7 のようになっているはずです。
リスト 7. document.rb
class Document < ActiveRecord::Base
belongs_to :user
belongs_to :subject
has_one :xml_content
# values displayed | stored
PLATFORM_TYPES = [ ['Neutral', 'Any'],
['Windows', 'WinXP'],
['Mac OS X', 'MacOS'],
['Linux', 'Linux']]
def uploaded_doc=(doc_field)
self.name = File.basename(doc_field.original_filename).gsub(/[^\w._-]/, '')
self.content_type = doc_field.content_type.chomp
self.size = doc_field.size
self.created_at = Time.now
unless self.content_type.include?('text/xml')
self.data = doc_field.read
else
content = XmlContent.new
content.name = self.name
content.data = doc_field.read
self.xml_content = content
end
end
end |
XML_CONTENTS 表と DOCUMENTS 表のモデルを定義したら、今度はアップロード機能と文書のリスト表示を変更します。サンプル・アプリケーションではアップロード機能と文書のリスト表示を更新して、表示対象のモデル属性
(列) を明示的に選択するようにしてあります。
/app/views/documents/list.rhtml ではこの明示的な列の選択を以下のように実装します。
リスト 8. /app/views/documents/list.rhtml
<table cellpadding="0" cellspacing="0">
<tr>
<th>ID</th>
<th>Document name</th>
<th>Subject</th>
<th>Shared by</th>
<th>Size</th>
<th>Update at</th>
<th>Platform</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @documents.each_with_index do |document,i| %>
<% row_class = i%2 ==0 ? "even" : "odd" %>
<tr class="<%=row_class%>">
<td><%= document.id %></td>
<td><%= document.name %></td>
<% if document.subject %>
<td><%= link_to "#{document.subject.name}",
:controller => 'subjects', :action => 'list' %></td>
<% else %>
<td></td>
<% end %>
<% if document.user %>
<td><%= link_to "#{document.user.userid}",
:controller => 'users', :action => 'list' %></td>
<% else %>
<td></td>
<% end %>
<td><%= number_to_human_size( document.size ) %></td>
<td><%= document.updated_at.strftime("%d/%m/%Y %I:%M%p") %></td>
<td><%= document.platform %></td>
<td><%= link_to 'Show', :action => 'show', :id => document %></td>
<td><%= link_to 'Edit', :action => 'edit', :id => document %></td>
<td><%= link_to 'Remove', { :action => 'destroy', :id => document },
:confirm => 'Are you sure?', :method => :post %></td>
</tr>
<% end %>
</table> |
次に、XML データの場合の文書表示機能を処理するため、コントローラーを以下のように更新しました。これらのエントリーは /app/controllers/documents_controller.rb
に追加します。
リスト 9. documents_controller.rb
def show
@document = Document.find(params[:id])
doc_type = @document.content_type
unless doc_type.include?('text/xml')
doc_content = @document.data
else
doc_content = @document.xml_content.data
end
send_data(doc_content,
:filename => @document.name,
:type => doc_type,
:disposition => "inline")
end |
さらに XML 文書のアップロードをサポートするために以下の更新も行いました。これを見ると、サブジェクトとユーザーの関係の処理に文書が実装されていることがわかります。
リスト 10. documents_controller.rb
def upload
if params[:document][:uploaded_doc].to_s.empty?
flash[:notice] = "Please provide a file for upload"
redirect_to(:action => "new" )
else
@document = Document.new(params[:document])
@subject = params[:subject_name] && params[:subject_name].empty? ?
Subject.new :
Subject.find_by_name(params[:subject_name])
Document.transaction do
User.find(session[:user_id]).documents << @document
@subject.documents << @document
@subject.size = @subject.documents.size
if @subject.new_record?
@subject.name = params[:subject][:name]
@subject.tag = params[:subject][:tag]
@subject.description = params[:subject][:description]
@subject.save
end
if @document.save
flash[:notice] = "Document #{@document.name} successfully created."
if @document.subject.subscriptions
SubscriptionMailer.deliver_notify(@document)
end
redirect_to :action => 'list'
else
render :action => 'new'
end
end
end |
以下の図 1 に、上記のマイグレーション・ステップを実行した後の Team Room に含まれるモデルの相互関係を示します。
図 1. 変更後のモデル間の新たな関係
Team Room への XML マーケティング・データのアップロード
 | |
注 3: IMPORT コマンドでインポートできるのは、整形式 XML 文書のみです。
注 4: XML スキーマを削除するには、以下のコマンドを使用します。
drop xsrobject teamroom.marketinfo
|
|
マイグレーションによってスキーマに必要な変更をすべて実装したので、今度は XML データを XML_CONTENTS 表にアップロードする作業に取り掛かります。複数の
XML 文書を表に挿入するのに最も簡単な方法は、DB2 9 データ・サーバーの IMPORT ユーティティーを使うことです。IMPORT ユーティリティーを使用すると、XML
スキーマの妥当性検証の有無に関わらず整形式 XML 文書をインポートすることができます。このサンプルでは、XML_CONTENTS 表と DOCUMENTS
表、そして SUBJECTS 表内の関連するエントリーを正しく確実に更新しなければなりません。そのため、生成された基盤を使用して一度に 1 ファイルずつデータのアップロードを行い、すべての外部キーの関連が維持されるようにします。
図 2 に記載した文書の表示 /app/views/documents/new.rhtml には、XML コンテンツに関連付ける文書の作成と、この新しい文書に関連付ける新規サブジェクトの作成が示されています。
図 2. Team Room リポジトリーへの XML 文書のアップロード
XQuery 検索と XPath 検索の演習で使用するカナダと米国の一部の地域をシミュレートしたマーケティング・データが含まれる残りの XML
文書をアップロードするには、今がちょうどいい機会です。XML ファイルが配置されているのは、/test/fixtures の下です。ここには検証のために使用する
marketinfo.xsd XML スキーマもあります。更新された Team Room アプリケーションを入手するには、この記事の終わりにある「ダウンロード」セクションを参照してください。
XML データ型での基本 CRUD 操作
ここで、XML データの管理方法を学ぶ最初の一歩として基本的な XML CRUD (Create, Retrieve, Update, and
Delete) 操作を実行してみましょう。
CRUD 操作を説明するため、ここではマーケティング・チームが顧客の居住都市の調査に関心を持っているとします。顧客の認知度を高めて店舗に再び足を運ばせるために、顧客が住む都市でのマーケティング・キャンペーンを企画しているという想定です。それに備えて私たちができるのは、XML_CONTENTS
表に保管されたマーケティング情報に関する XML 文書からデータを抽出することです。さらに、拡大し続けている対象都市のリポジトリーを (継続的に)
追跡するために作成された調査情報を保管することにしました。この都市のデータベースを XML として作成すれば、別の表を作成してデータを追跡することを心配しなくても済むことに注目してください。情報を
XML 文書として作成して、同じ列に XML として挿入しなおせばいいだけの話だからです。今後のある時点で、DBA がアプリケーション・データと分けるために新しい表を作成することを考えるかもしれませんが、それは完全に論理的なセマンティック上の要件なので、このデータベースには必要のないことです。
DB2 では、XML データに対してクエリーを実行するのに SQL、XQuery、あるいはこの 2 つの組み合わせを使用できます。この例では
XQuery を使います。XQuery を使用すると、返されたクエリーの結果を XML 文書を構成する際の値として使用するのが簡単だからです。リスト 11 に、実行する XQuery を記載します。
リスト 11. 都市調査の XQuery
XQUERY
<cities>
declare default element namespace "http://www.ibm.com/developerworks";
{ for $c in fn:distinct-values(
db2-fn:xmlcolumn(
'XML_CONTENTS.DATA')/marketinfo/sales/customer/address/city)
order by $c
return <city>{$c}</city>
}
</cities> |
まずはステートメント内にある関数 db2-fn:xmlcolumn() から、この特定の XQuery がどのように動作するかを説明していきます。db2-fn:xmlcolumn() は、現在接続している DB2 データベースの XML 列からシーケンスを取得する関数です。上記では、XML_CONTENTS 表の DATA
列からデータを取得しています。ただし、必要なのは DATA 列に含まれるすべてのデータではなく、XPath 式 /marketinfo/sales/customer/address/city で識別されるサブセットだけです。
つまり、表に含まれるすべての行のすべての XML 文書を調べ、XPath に出てくる city 要素すべてを選択するわけですが、これによって問題が発生する可能性があります。複数の顧客が同じ都市に住んでいることが考えられるためです。その点を考慮して使用しているのが、XQuery
関数fn:distinct-values() です。名前が示すように、この関数は他と重複しない city 要素のシーケンスだけを返すため、都市名が繰り返されることはありません。このシーケンスは変数
$c に割り当てられます。
最後に$c の都市を並べてから、結果を返します。XQuery を極めて有能にしている特徴の 1 つは、返されるデータのフォーマットを高度にカスタマイズできるという点です。今のところ都市名のシーケンスはあるので、それぞれの
city 要素を <cities> 要素でラップします。これは要素のシーケンスですが、ルート要素がないため妥当な XML 文書にはなっていません。妥当な XML 文書が返されるようにするには、結果全体を
<cities> 要素のなかに配置します。すると、 リスト 12 のような昇順のデータが返されます。
リスト 12. 返される典型的な XML データ
<cities>
<city>Atlanta</city>
<city>Augusta</city>
<city>Austin</city>
<city>Baton Rouge</city>
<city> ... </city>
</cities> |
DB2 9 での XQuery の詳細は、DB2 XML Guide を参照してください (記事の終わりにある「参考文献」にリンクを記載しています)。
都市のリストが XML 文書になったので、今度はこの文書をデータベースに戻します。開発者は、あらゆる型のデータをデータベースに挿入する場合と同じように
XML 文書を挿入するはずです。
リスト 13. XML 文書の挿入
class DocumentsController < ApplicationController
[...]
def upload
[...]
@document = Document.new(params[:document])
@subject = params[:subject_name] && params[:subject_name].empty? ?
Subject.new :
Subject.find_by_name(params[:subject_name])
Document.transaction do
User.find(session[:user_id]).documents << @document
@subject.documents << @document
@subject.size = @subject.documents.size
if @subject.new_record?
@subject.name = params[:subject][:name]
@subject.tag = params[:subject][:tag]
@subject.description = params[:subject][:description]
@subject.save
end
if @document.save
flash[:notice] = "Document #{@document.name} successfully created."
[...]
end |
顧客の数は常に静的であるわけではないので、リストを最新の状態に維持するには顧客の都市リストを例えば毎週、あるいは毎月定期的に更新しなければなりません。それには、同じ
XQuery ステートメントを実行し、現在データベース内にある文書をその id に基づいて最新バージョンの XML ファイルに置き換えます。注目すべき点は、既存の文書を隠し
:id パラメーターで取得する方法です。このパラメーターが、DocumentsController で使われる編集表示フォーム (/app/views/documents/_form.rhtml) に渡されます。
リスト 14. XML 文書の更新
class DocumentsController < ApplicationController
[...]
def update
@document = Document.find(params[:document][:id])
if @document.update_attributes(params[:document])
flash[:notice] = 'Document was successfully updated.'
redirect_to :action => 'show', :id => @document
else
render :action => 'edit'
end
end
[...]
end |
内部では DB2 が既存の XML データ・ページの割り当てを解除し、新しい値が挿入し直されます。このように効率的に、XML 文書全体が更新された新しい文書に置き換えられるというわけです。
このデータがいずれ無用になったり、あるいは他のメカニズムで処理されるようになることも考えられます。例えば、DBA が別の表の別の XML 列を使用するなどしてコンサーン
(対象) の分離をしようと主張した場合です。 文書を削除するには、適切な WHERE 述部を使って DELETE 文を実行します。 Ruby
では、例えば以下のように実行できます。
リスト 15. XML 文書の削除
class DocumentsController < ApplicationController
[...]
def destroy
Document.find(params[:id]).destroy
redirect_to :action => 'list'
end
end |
XML データに対するクエリーの実行
基本 CRUD 操作だけでなく XQuery 言語の機能を使用すれば、どのようなクエリーを作成して、どのような形でデータが返されるようにするかを極めて詳細に指定することができます。例えば、顧客が住んでいる都市のリストも役立ちますが、マーケティング・チームではそれよりも、どの製品カテゴリーがアクティブであるかにもっと興味があるかもしれません。どの製品に人気があるかがわかれば、マーケティング・キャンペーンですべての製品を網羅するだけの予算を見込めないマーケティング・イニシアチブの焦点をさらに絞り込むことができます。アクティブな製品であることを示す
1 つの測定指標は、特定の製品カテゴリーで最近販売実績があったかどうかです。もっと限定すれば、これらの製品カテゴリーが特定の期間中にアクティブであったかどうかです。
データ・マイニング作業は、2007年4月15日から 4月30日の間に販売実績があった製品カテゴリーは何かという質問を提示することから始めます。
リスト 16 は、マーケティング・チームが提示した質問に答えるためのクエリーです。
リスト 16. 4月15日から 4月30日までの製品カテゴリーの購入情報を取得するための XQuery
XQUERY
declare default element namespace "http://www.ibm.com/developerworks";
<categories>
{ let $categories := fn:distinct-values(
for $c in db2-fn:xmlcolumn( "XML_CONTENTS.DATA")/marketinfo/sales/customer
where xs:date($c/last_purchase) > xs:date("2007-04-15")
and xs:date($c/last_purchase) <= xs:date("2007-04-30")
return $c/categories/category/@type)
for $c in $categories
return <category>{$c}</category>
}
</categories> |
前の例と同じく妥当な XML 文書を返す必要があるので、XQuery の結果を <categories> ルート XML 要素でラップします。ラップされた XQuery は 2 つの部分で構成されます。まず、$categories 変数に式をバインディングする「let」文、そしてこの変数が for 文のコンテキストで使われる部分です。それぞれについて個別に検討してみましょう。
「let」文自体は XQuery 式 の FLWOR (for、let、where、order by、return) で構成されます。ご存知かもしれませんが、FLWOR
表現はよく SQL の SELECT-FROM-WHERE ブロックと比較されます。上記ではまず、for $c in db2-fn:xmlcolumn("XML_CONTENTS.DATA") /marketinfo/sales/customer ですべての顧客を繰り返し処理しています。購入日の条件は、2007年4月15日から 2007年4月30日までの間であり、マーケティング・チームが知りたいのはこの基準を購入情報が満たすかどうかなので、where xs:date($c/last_purchase) > xs:date("2007-04-15") and
xs:date($c/last_purchase) <= xs:date("2007-04-30") としています。
基準を満たす購入情報のなかからは、XML 属性として格納されたカテゴリーのタイプ $c/categories/category/@type を返します。前回の例と同じく、同じカテゴリーが重複しないように fn:distinct-values() 関数を使用します。このカテゴリー・リストが、$categories 変数にバインドされます。
この時点で $categories には重複のないカテゴリーのシーケンスが保管されることになりますが、この例で返すのは XML なので、シーケンスに含まれるそれぞれの値を <category> XML 要素でラップします。最終的な出力は、リスト 17 のようになります。このリストは確実に、4月15日から 4月30日までの購入情報が含まれるようにフィルタリングされています。
リスト 17. 、4月15日から 4月30日までに販売実績があった製品カテゴリーのデータ例
<categories>
<category>Home</category>
<category>Electronics</category>
<category>Apparel</category>
<category>Gifts & Flowers</category>
<category>Baby</category>
</categories> |
この XQuery をパラメーター化すれば、マーケティング・チームが望むどんな日付の範囲のクエリーでも実行できるようになります。さらに、このクエリーをカプセル化するために、この機能をストアード・プロシージャーにして、Rails
開発者用に XQuery の詳細を抽象化することができます。
図 3 に記載する文書のリスト表示 /app/views/documents/list.rhtml のレイアウトには、XQuery 検索と XPath 検索を実行できる事前ビルド済みのレポート・フォームも含まれています。
図 3. Team Room の文書リポジトリーと事前ビルドされたマーケティング・レポート
巨大な XML データに対するクエリーの実行
クエリーの方法に慣れてきたところで、マーケティング・チームのデータに対する要求が幾分厳しくなってきました。今度は、特定の郵便番号が含まれる地域での特定のカテゴリー・タイプの製品販売数を知りたがっています。この郵便番号は、大量の販売数があった地区であることも、苦情がたくさん寄せられた地区であることもあります。このような情報があれば、マーケティング・マネージャーと製品マネージャーは、どの販売地域でどの製品に人気があるかを該当する郵便番号と結び付けて判断することができます。そして地域での製品の人気に応じて、販売を打ち切るべき製品や、マーケティング活動で重点を絞るべき郵便番号を見極められるはずです。
この要求に応えるために使うのは、SQL/XML 関数 XMLQUERY() です。SQL/XML を使ってクエリーを実行すると、両方の機能を合わせて使うことができます。例えば、SQL/XML
では以下の操作を実行することができます。
- リレーショナル・データと XML データの両方で述部を使用する
- XML データのフラグメントにアクセスして抽出する
- SQL レベルで XML データの集約とグループ化を使用する
- リレーショナル・データと XML データを結合する
- パラメーターを XQuery 式に渡す
ここで説明する XQuery では上記のうち、(1)、(2)、(5) を使用します。簡潔にするためサンプルをパラメーター化することはせずに、郵便番号「79081」でのカテゴリー・タイプ「Jewelry
(ジュエリー)」に興味があるという前提にします。マーケティング・チームにとって幸いなことに、付属のアプリケーション・コード (この記事の終わりにある「ダウンロード」セクションを参照) に記載されているクエリーのバージョンでは、パラメーターをあらゆるカテゴリー・タイプまたは郵便番号にすることができます。
リスト 18. 特定の郵便番号で販売された全カテゴリー・タイプを取得する SQL/XML クエリー
select name, xmlquery(
'declare default element namespace "http://www.ibm.com/developerworks";
let $total := sum (
for $i in $t//category
let $sum := count($i/item)
where $i/@type = "Jewelry"
return $sum
)
return <total>{$total}</total>'
passing data as "t"
) as data
from teamroom.documents
where xmlexists(
'declare default element namespace "http://www.ibm.com/developerworks";
$t/marketinfo/sales/customer/address[zip = "79081"]'
passing data as "t"
) |
上記では、SELECT 文を使用して、name 列で識別された領域ごとに 1 行返しています。さらにそれぞれの行について、<total> ルート要素だけが含まれる有効な xml 文書を返します。ここでは XQuery を SQL 言語のなかで呼び出しているため、SQL/XML
関数 XMLQUERY() を使って XQuery エンジンが操作対象のコンテキストを認識するようにしなければなりません。これまでの例では db2-fn:xmlcolumn() 関数を使用して対処していましたが、今回はデータをtとして渡す節を使用します。つまり、データを操作対象の XML 列として識別し、変数 tを割り当てるということです。$t 変数が XQuery 内のどこにあっても、DB2 はそれを認識し、SELECT 文の現在の行に含まれる data 列の XML 文書を置換します。
SQL レベルで行われているもう 1 つの異なる点は、XMLQUERY() を列内のすべての XML 文書に対して実行しているわけではないという点です。クエリーを実行するのは郵便番号が「79081」の顧客が含まれる行に制限し、該当する行を見つけたら、それらの行でだけ
XQuery を実行します。具体的には、その行に含まれるすべての顧客に対しては、郵便番号が「79081」であるかないかに関わらずクエリーを実行します。
このフィィルタリングを行うのが、SQL/XML XMLEXISTS() 関数述部を使用した SQL WHERE 節です。XMLEXISTS 述部は、XQuery クエリーが 1 つ以上の要素のシーケンスを返すかどうかを判断します。上記の例で XMLEXISTS() に渡している XQuery 式は、単純な XPath 式で $t は XML 列 data: $t/marketinfo/sales/customer/address[zip
= "79081"] です。
この XPath 式は、zip が「79081」の address 要素のシーケンスを返すという意味に解釈できます。指定された XPath が空のシーケンスを返すと
XMLEXISTS は false を返し、そうでなければ true を返します。SQL によって XML 文書のサブセットを取得できたので、今度は
XQuery で $total 変数への let バインディングを使う番です。$total には FLWOR 式の結果が割り当てられます。XML 文書に含まれる、タイプが「ジュエリー」(where $i/@type = "Jewelry") のすべての <category>要素 (for $i in $t//category) について、販売数をカウントしてその値を $sum に割り当て (let $sum := count($i/item))、合計を返します (return $sum)。
上記のコードによって返されるのは、顧客ごとの合計のシーケンスです。この結果が $sum 関数に渡され、すべての顧客でその特定カテゴリーについての合計が取得されます。ここで $total を返すわけですが、これは XML でなければならないので <total> 要素でラップします (return <total>{$total}</total>')。最終的には、return <total>198</total>' のような結果になります。
DB2 9 で XQuery を使用する方法の詳細と例については、DB2 XML Guide (「リソース」セクションにリンクを記載) を参照してください。
XML データのシュレッディング
例えば 1 つのシナリオとして、新しくビルドしたアプリケーションがあり、これをネイティブ XML に対応していないレガシー・アプリケーションとリポジトリーに統合しなければならないとします。この場合、XML
文書に保管された情報をリレーショナル表オブジェクトに配置しなければなりません。
あるいは市場分析の結果をレガシー・アプリケーションで分析するためにリレーショナル・ストアに提供するというシナリオを考えてみてください。それには
XML データのシュレッディングを行いリレーショナル表に挿入できるようにする必要があります。
すでに説明したように、pureXML には XML データを直接操作するための多数の機能があります。必要であれば、pureXML では分解としても知られるシュレッディングを実行することもできます。DB2
9 データ・サーバーには、XML のシュレッディングを簡単に行える以下のツールが組み込まれています。
1. XDBDECOMPXML ストアード・プロシージャー
DB2 9 のアノテーション付き XML スキーマ分解機能を使用すると、XML 文書をリレーショナル表に分解することができます。名前が示すとおり、この機能は
XML スキーマのアノテーション (注釈) をマッピング言語として使用して XML 文書内の情報をリレーショナル表にマッピングします。それには
XML スキーマが必要になるため、XML スキーマ文書を DB2 XSR に保管して分解用に指定してください。XML 文書をマッピングされたリレーショナル列に分解、あるいは「シュレッディング」するには、DB2
ストアード・プロシージャーの呼び出し、または CLP (Command Line Processor) コマンドを使用します。
Visual Studio アドインによる XML シュレッディングのサポートに関する詳細は、「Introduction to annotated XML schema decomposition using the DB2 Visual
Studio 2005 Add-in」を参照してください。
XML スキーマにアノテーションを付けるには、DWB (DB2 Developer Workbench) を使うという方法もあります。無料でダウンロードできる
DWB は、ストアード・プロシージャーとユーザー定義関数の開発を含め、DB2 データベース・アプリケーションの作成、編集、デバッグ、デプロイ、そしてテストを行うための包括的な環境です。ダウンロード情報については、この記事の終わりにある「ダウンロード」セクションを参照してください。
DWB には、コンポーネントの 1 つとしてアノテーション付き XML スキーマ分解マッピング・エディターがあります。このエディターでは単純かつ直観的なグラフィカル・インターフェースを使って、XML
スキーマとリレーショナル・スキーマとの関係をマッピングすることができます。グラフィカル・インターフェースにより XML 要素または属性を DB2
のリレーショナル列にマッピングすると、自動的に XML スキーマ文書にアノテーションが付けられます。XML スキーマを保管した後、XSR に登録すれば、XML
文書を DB2 に分解する用意は完了です。
XML スキーマのアノテーションおよびストアード・プロシージャーの xdbDecompXML 一式についての詳細は、この記事の範囲外なのでここでは説明しません。DB2
9 XML Guide (「参考文献」を参照) に、アノテーション付き XML スキーマ分解の詳細、さらにコンテンツに基づく条件付き分解、そして適用するコンテンツ変換を指定してから挿入するなどの高度な機能について記載されています。XML
Extender とその分解用のメソッドについての知識がある方は、developerWorks の記事「From DAD to Annotated XML Schema Decomposition」(Mayank Pradhan 著) でさらに詳しいことを調べてください。
2. XMLTABLE SQL 表関数
XMLTABLE は、XML 文書での XQuery 式を評価して表を返す SQL 表関数です。返される表には、XML をはじめとするあらゆる
SQL データ型の列を含められます。XMLTABLE を INSERT 文と組み合わせて使用すると、値を XML 文書内から取得して、さらにリレーショナル表に値を挿入することができます。
これによって、アノテーション付き XML スキーマ分解と同じ機能を実現します。XMLTABLE と INSERT 文との組み合わせは、「Insert-from-XMLTABLE」文と呼ばれることもあります。
この方法は XML シュレッディングを簡単にするだけでなく、XML 文書のフラグメントをリレーショナル表のさまざまな列に保管するのにも理想的です。
以下のステートメントは、XQuery 式を実行し、DATA 列の値を表として返します。それぞれの行には、特定の顧客に関連付けられた都市、州、郵便番号、そして前回の購入日が示されます。
リスト 19. XMLTABLE 関数
SELECT X.CITY, X.STATE, X.ZIP, X.LAST_PURCHASE FROM
TEAMROOM.XML_CONTENTS,
XMLTABLE (XMLNAMESPACES (DEFAULT 'http://www.ibm.com/developerworks'),
'db2-fn:xmlcolumn("XML_CONTENTS.DATA")//customer'
COLUMNS
"CITY" CHAR(16) PATH './address/city',
"STATE" CHAR(16) PATH './address/state',
"ZIP" CHAR(6) PATH './address/zip',
"LAST_PURCHASE" DATE PATH './last_purchase') as X |
以下は、上記の XQuery の出力例です。
リスト 20. XMLTABLE の出力例
Baton Rouge LA 77888 03/10/2007
Baton Rouge LA 14257 01/07/2007
Richmond VA 78045 01/26/2007
Oklahoma City OK 71107 04/13/2007
Tallahassee FL 41720 04/25/2007
Richmond VA 39591 03/25/2007
Richmond VA 36522 03/23/2007
Richmond VA 32230 02/12/2007
Charleston WV 33015 02/12/2007
Columbia SC 72647 01/11/2007
Raleigh NC 11238 04/02/2007
Nashville TN 21245 01/06/2007
Fankfort KY 53793 04/18/2007
Austin TX 35462 03/13/2007
Columbia SC 68359 01/01/2007
Jackson MS 25770 01/20/2007
Little Rock AR 46342 03/10/2007
Tallahassee FL 54306 01/20/2007
Charleston WV 44339 02/20/2007
Frankfort KY 92403 02/27/2007
<etc ........> |
適切な SQL タイプを定義した CUSTOMER_INFOS 表が定義されていれば、XML のマーケティング情報 (表形式) から抽出したこの顧客データをさらにリレーショナル表に挿入することもできます。それには、以下のように
SELECT 文を INSERT 文でラップします。
リスト 21. 分解した XML データのリレーショナル表への挿入
INSERT INTO TEAMROOM.CUSTOMER_INFOS
SELECT X.CITY, X.STATE, X.ZIP, X.LAST_PURCHASE FROM
TEAMROOM.XML_CONTENTS,
XMLTABLE (XMLNAMESPACES (DEFAULT 'http://www.ibm.com/developerworks'),
'db2-fn:xmlcolumn("XML_CONTENTS.DATA")//customer'
COLUMNS
"CITY" VARCHAR(16) PATH './address/city',
"STATE" CHAR(16) PATH './address/state',
"ZIP" CHAR(6) PATH './address/zip',
"LAST_PURCHASE" DATE PATH './last_purchase') as X |
さらに、Ruby には Ruby Electric XML (REXML) という XML プロセッサーも付属しています。このプロセッサーを使用すれば、XML
データのツリー解析とストリーム解析も可能です。詳細については、「参考文献」セクションを参照してください。
Web 2.0 世界の実現
連載第 1 回では、ユーザーが特定のサブジェクトの更新をサブスクライブするための機能を紹介しました。更新が行われると、ユーザーには E メールで通知が届きます。これでは古臭くていかにも
20 世紀的です。Web 2.0 という新しい世界が開けている今、このような通知を自分の好みのリーダーで RSS や Atom フィードとして受信したいと思うユーザーもいるはずです。
DB2 9 pureXML を使用すれば、ユーザーがそれぞれに好むリーダーで簡単にポーリングできるフィードを生成することができます。そのために使うのが、SQL/XML
XML パブリッシング関数です。XML パブリッシング関数を使って XML ノードおよび文書を構成すると、リレーショナル・データと XML データの両方を使用することができます。
注: パプリッシング関数は、コンストラクター関数と呼ばれることもあります。
DB2 9 には、以下の XML パブリッシング関数が用意されています。
- XMLNAMESPACES
- XMLELEMENT
- XMLATTRIBUTE
- XMLFOREST
- XMLDOCUMENT
- XMLCONCAT
- XMLCOMMENT
- XMLPI
- XMLTEXT
それでは、対象のサブスクリプションが更新されるとユーザーに通知する Atom フィードを提供してみましょう。ここでは Atom フィードを使いますが、DB2
では RSS フィードを生成することも同じく可能です。目的のフィードに対応する XML スキーマを使ってフィードを構成すればいいだけの話です。
リスト 22. Atom フィード・ストアード・プロシージャー
CREATE PROCEDURE GET_ATOM_FEED ( )
DYNAMIC RESULT SETS 1
------------------------------------------------------------------------
-- SQL Stored Procedure
------------------------------------------------------------------------
P1: BEGIN
-- Declare cursor
DECLARE cursor1 CURSOR WITH RETURN FOR
SELECT XMLSERIALIZE(
XMLDOCUMENT(
XMLELEMENT (NAME "feed",
XMLNAMESPACES(DEFAULT 'http://www.w3.org/2005/Atom'),
XMLCONCAT (
XMLELEMENT (NAME "id", 'http://localhost:3000/documents'),
XMLELEMENT (NAME "title", 'Teamroom Documents'),
XMLELEMENT (NAME "updated", CURRENT TIMESTAMP),
XMLELEMENT (NAME "link",
XMLATTRIBUTES('http://localhost:3000/documents/atom_feed'
as "href", 'self' as "rel")),
XMLELEMENT (NAME "author",
XMLCONCAT(
XMLELEMENT (NAME "name", 'TeamRoom'),
XMLELEMENT (NAME "email", 'teamroom@developerWorks.ibm.com')
)
),
XMLAGG (
XMLELEMENT (NAME "entry",
XMLCONCAT (
XMLELEMENT (NAME "title", name),
XMLELEMENT (NAME "id", 'http://localhost:3000/documents/show/'
|| CHAR(id)),
XMLELEMENT (NAME "updated", updated_at),
XMLELEMENT (NAME "link", 'http://localhost:3000/documents/show/'
|| CHAR(id)),
XMLELEMENT (NAME "category", category),
XMLELEMENT (NAME "summary", content_type),
XMLELEMENT (NAME "content", XMLATTRIBUTES('text' as "type"), content)
)
)
)
)
)
)
AS CLOB INCLUDING XMLDECLARATION
)
FROM (SELECT d.id as id, d.name as name, d.content_type as content_type,
d.updated_at as updated_at, s.name as category, s.description as content
FROM DOCUMENTS d, SUBJECTS s
WHERE d.subject_id = s.id
ORDER BY d.updated_at DESC
FETCH FIRST 10 ROWS ONLY)
AS doc_list;
-- Cursor left open for client application
OPEN cursor1;
END P1 |
この機能をストアード・プロシージャーとして提供しているのは、Rails 開発者が Atom フィードの生成についての詳細を気にしなくても済むようにするためです。開発者が以下のようにストアード・プロシージャーを呼び出せばいいだけにしてあります。
リスト 23. Atom フィード・ストアード・プロシージャーの呼び出し
class Document < ActiveRecord::Base
[...]
def atom_feed
feed = Document.find_by_sql("call teamroom.get_atom_feed()")
content = feed[0].attributes["1"]
send_data(content,
:filename => 'TeamRoomFeed.atom',
:type => 'text/xml',
:disposition => "inline")
end
[...]
end |
DB2 XML パブリッシング関数と Atom 配信フォーマット (Atom Syndication Format) のプロトコルの詳細についてはこの記事では触れません。詳しい情報と参考になるリンクについては、「参考文献」 セクションを参照してください。また、ダウンロードできるアプリケーションには、ストアード・プロシージャーや、フィードを作成するためのアプリケーション・コードを含め、Atom
フィードの完全な実装が含まれています。
まとめ
DB2 の pureXML では XML データをネイティブ階層フォーマットで保管できます。これにより、DB2 リレーショナル・データベース管理システムに備わったパフォーマンス、スケーラビリティー、信頼性、そして可用性というメリットを提供すると同時に、アプリケーション同士が
XML の共通言語で簡単に対話することができます。さらに Ruby on Rails フレームワークの単純さと柔軟性を組み合わせれば、開発者が世界規模の
Web 2.0 アプリケーションを素早く簡単にビルド、デプロイ、そして管理することも可能になります。開発オプションには柔軟性があるため、ビジネスのニーズに合わせて情報をリレーショナル・フォーマットから階層フォーマットに移行することができます。データ・フォーマットを変更しても、パフォーマンスが落ちることはありません。使用している最適化技術は同じで、しかも
DB2 技術がもたらすバックアップ、復元、そしてスケーラビリティーというその他すべての信頼性と可用性が適用されるからです。
DB2 pureXML はネイティブ XML の生成をサポートするだけでなく、事後解析されたデータをノード・レベルの粒度でディスクに保管します。このため、索引付け
(実際の文書ノードの位置) や DB2 のクエリー・エンジンに組み込まれた XQuery および XPath プリミティブを使用することによって、優れたクエリー・パフォーマンスが実現します。そんな
DB2 pureXML は、極めて使いやすい XML ライブラリー一式 (REXML、ROXML など) を提供する Ruby on Rails
ランタイムにはうってつけの組み合わせであるとともに、XML ベースのデータ・ストアを Web アプリケーションに短時間で統合することを可能にします。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Team room sample code | Teamroom2.zip | 10KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | John Chun は、アプリケーション開発およびツールの分野で活躍する DB2 Advanced Support チームのスペシャリストです。IBM DBT Toronto Lab でこれまで 7 年間、Java、C、C++、Perl、REXX、C# をはじめとする各種言語での DB2 アプリケーション問題の解決に取り組んできました。DB2 CLI および OLEDB ドライバー、そして .NET データ・プロバイダー関連の多数のプロジェクトにも参加しています。彼は DB2 認定ソリューション・エキスパート、認定 Websphere アドミニストレーターでもあります。 |
 | |  | Christine Law は、IBM Toronto Lab のシニア DB2 スペシャリスト兼 IBM 認定エキスパートです。JDBC、SQLJ、ストアード・プロシージャー、そして組み込みSQL を専門とする彼女は、さまざまなプログラミング言語とスクリプト言語を使った Linux、UNIX、Windows プラットフォームでの広範なアプリケーション開発経験を持っています。最近では、AJAXや Ruby などのオープン・ソース技術にも関心を寄せています。 |
 | |  | Salvador は、2002年からカリフォルニア州サンノゼの IBM Silicon Valley Labs で働いています。現在は、DB2 pureXML を含め、IBM のデータ・サーバー製品を対象としたランタイムおよびツール技術に取り組んでいます。 |
 | |  | Alex Pitigoi は、IBM Toronto Lab の顧問ソフトウェア・エンジニアです。1998年以来、Web 技術とデータベース管理を専門に、情報管理の分野でのさまざまなソフトウェア開発プロジェクトに携わっています。最近では、SQLModel プロジェクトの開発を指揮しました。この開発は現在、Eclipse Data Tools プロジェクトならびに複数の IBM データ・サーバーで、データベース管理 Web ツールの全体的アーキテクチャーに組み込まれています。彼は DB2 Satellite Administration Center、IBM Express Runtime にも取り組んでおり、DB2 を対象とした最初の Web Tools セットの開発も主導しています。現在重点的に取り組んでいるのは、新しいオープン・ソース技術
(Ruby、Python、PHP) へのIBM データ・サーバー対応化です。 |
記事の評価
|