|
今年も各携帯キャリアが冬モデルを発表!買う予定はありますか?
|
XQueryの制御構造の活用XQueryの特徴以前、データベースプログラマである同僚の一人が、初期のXQueryの実装をしばらく使ってみて、この言語をニコニコ言語と評しました。それは言語のわかりやすさと関係するのかと訊いたところ、彼はハンガリー訛りの英語でこう答えました。「まさか、言語自体は難物だよ。だけど、ニコニコマーク (: :) がコメントの区切り記号になっているから、たとえデータベースがずたずたでもニコニコしてしまうわけさ」。彼の意見はこの言語をあからさまに非難するものではありませんが、XQueryについてのある事実、つまりXQueryの構造は一般の言語に似ているが、その違いの部分で大きくつまずく場合があるということをよく示しています。XQueryは習得の困難な言語ではありませんが、どうしてうまく動かないか知ろうという気にさせる言語でもあります。 XQueryの制御構造XQueryの制御構造はFLOWRという風変わりな頭字語で呼ばれてきました。これは、その言語で使われる特に重要なXQuery構造(すべてではない)の略称で、FLOWR自体は次の5つの操作を表します。
XQueryの使いどころXQueryは''集合操作''言語です。単一のスカラー値よりも情報の集合を操作することに主眼が置かれます。また、集合操作言語という点で、その仕様に組み込まれているXPath 2.0言語を置き換えるよりも、それを拡張することを目指しています。実際、突き詰めていくと、XQueryの大部分はXPathの制御言語をラップする仕組みの1つに過ぎず、そのやり方はXSLTがXPathにテンプレート言語を結び付けるのとどこか似ています。こうした理由により、XQueryを操作するとき、この言語を最も効果的に使うには、最初にXPath 2内で可能な限り多くのことを行い、XPath 2ではもう立ち行かなくなったところでXQueryのコマンド構造を利用するという方法をとるべきです。 たとえば、従業員レコードのコレクションで構成されるXMLデータソースがあると仮定します。現在、それらはファイル「employees.xml」に入っています(リスト1を参照)。 リスト1 Employees.xml
<?xml version="1.0" encoding="UTF-8"?>
<employees>
<employee id="be129">
<firstname>Jane</firstname>
<lastname>Doe</lastname>
<title>Engineer</title>
<division>Materials</division>
<building>327</building>
<room>19</room>
<supervisor>be131</supervisor>
</employee>
<employee id="be130">
<firstname>William</firstname>
<lastname>Defoe</lastname>
<title>Accountant</title>
<division>Accts Payable</division>
<building>326</building>
<room>14a</room>
</employee>
<employee id="be131">
<firstname>Jack</firstname>
<lastname>Dee</lastname>
<title>Engineering Manager</title>
<division>Materials</division>
<building>327</building>
<room>21</room>
</employee>
<employee id="be132">
<firstname>Sandra</firstname>
<lastname>Rogers</lastname>
<title>Engineering</title>
<division>Materials</division>
<building>327</building>
<room>22</room>
</employee>
<employee id="be133">
<firstname>Steve</firstname>
<lastname>Casey</lastname>
<title>Engineering</title>
<division>Materials</division>
<building>327</building>
<room>24</room>
</employee>
<employee id="be135">
<firstname>Michelle</firstname>
<lastname>Michaels</lastname>
<title>COO</title>
<division>Management</division>
<building>216</building>
<room>264</room>
</employee>
</employees>
for $employee in doc("employees.xml")/employees/employee
return $employee[/s]
リスト2 XQueryスクリプトforSeq1.xqをSaxonで出力した例
result:sequence xmlns:result="http://saxon.sf.net/xquery-results"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<result:element>
<employee id="be129">
<firstname>Jane</firstname>
<lastname>Doe</lastname>
<title>Engineer</title>
<division>Materials</division>
<building>327</building>
<room>19</room>
<supervisor>be131</supervisor>
</employee>
</result:element>
<result:element>
<employee id="be130">
<firstname>William</firstname>
<lastname>Defoe</lastname>
<title>Accountant</title>
<division>Accts Payable</division>
<building>326</building>
<room>14a</room>
</employee>
</result:element>
</result:element>
<!-- more results -->
</result:sequence>
<employee_set>{
for $employee in doc("employees.xml")/employees/employee
return $employee}
</employee_set>
リスト3 ForSeq2.xqの結果
<result:sequence xmlns:result="http://saxon.sf.net/xquery-results"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<result:element>
<employee_set>
<employee id="be129">
<firstname>Jane</firstname>
<lastname>Doe</lastname>
<title>Engineer</title>
<division>Materials</division>
<building>327</building>
<room>19</room>
<supervisor>be131</supervisor>
</employee>
<employee id="be130">
<firstname>William</firstname>
<lastname>Defoe</lastname>
<title>Accountant</title>
<division>Accts Payable</division>
<building>326</building>
<room>14a</room>
</employee>
<!-- more employees -->
</employee_set>
</result:element>
</result:sequence>
for $item in $seqという式は、やや誤解を招く可能性があります。基本的にfor文はシーケンス内を反復処理するので、$item変数にはシーケンス内の各項目への内部ポインタが順に渡され、その$itemのコピーが渡されることはありません。つまり、$itemコンテキスト変数は、基本となるXML(または関連する)データモデル内の構造を参照していることと、その結果がこのコンテキストに基づくシーケンスになるという点において、「ライブ(live)」であると言えます。たとえば次の式では、
for $employee in doc("employees.xml")/employees/employee
order by $employee/lastname ascending
return $employee
order by文はリストを指定の条件で並べ替えた仮想的なシーケンスを作ります。
for $employee in doc("employees.xml")/employees/employee order by $employee/lastname ascending return $employee
letコマンドの利用誰がどう考えても、このような複雑な式を繰り返し入力するのは面倒です。幸い、letコマンドを使用して、このシーケンスを保持する一時変数を作成することができます。
let $sorted-seq := for $employee in doc("employees.xml")/employees/employee
order by $employee/lastname ascending
return $employee
letでは単一のスカラー値しか保持できないと考えてしまうと、この文の意味がすぐにはわからないでしょう。しかし、let文ではスカラー値だけでなくシーケンスも(さらに高度なデータ構造も)保持できるということを知れば、この式の意味合いがよくわかるはずです。また、作成されたシーケンスはXML構造内の特定の要素をポイントしているので、この並べ替えられたシーケンスは実体的にはポインタのシーケンスに過ぎず、(普通は巨大となる)XML構造そのものではありません。これにより、段階的なフィルタリング機構を驚くほど低コストで手際よく作成することができます。たとえば、従業員を姓でソートし、そのソート済みリストのレコード11から20までを出力するような式を作成するものとします。次のコードは、その1つの方法を示しています。
let $employees := doc("employees.xml")/employees/employee
let $sorted-employees := for $employee in $employees
order by $employee/lastname ascending
return $employee
let $paged-employees := subsequence($sorted-employees,10,10)
return $paged-employees
let代入文では、実際にはノードのシーケンスが処理されています。具体的には、「employees.xml」ドキュメント内のノードの初期集合、同じコンテンツのソート済みシーケンス、そしてソート済み従業員リストから取り出したサブシーケンスです。いずれのケースについても、ここでは要素のポインタだけが抽出されます。このパラダイムは効率的なクエリを作成するのに非常に有効です。XMLデータの大きなブロックを移動したりXMLデータベースの配置を変更したりしないで、ポインタのリストを操作するだけで済むため、操作が桁違いに高速化されるからです。ただし、このやり方には1つ注意すべき点があります。このポインタ操作が成立するためには素の結果(たとえば $employee)が返されることが前提になることです。結果を変更するようなものがあれば、それは結局新しい情報ノードとなり、たとえ無意味な変更であってもXQueryエンジンはそれらのノードを効率的に逆参照する必要があります。上記のコードはまったく同じ結果を返しますが([]で括られた結果は、XQueryの式を評価して、ストリーム内で結果を置き換えることを意味する)、1つ目の return文で新しいコンテンツが作成されるため、かなりコストがかかり、ずっと低速の操作になります。一般に、データセットをできるだけ小さなシーケンスに絞り込むまでは、新しいコンテンツを作成するのを控えるべきです。実際、かなり大きくなる可能性のあるデータセットに対してクエリを実行するときは、検索結果のフィルタリングと表示のために案外普通の操作パターンが使われるものです。
doc()関数は、絶対URLか相対URLを引数に取り、URLのコンテンツをXMLドキュメントとして解析しようとします。一方、collection()関数は、外部ソースからノードのコレクションを取得します。そのコレクションに親要素が存在する必要はありません。この違いはXMLファイルを取得するときはあまり意味を持ちませんが、多くのXMLデータベースは(単一の要素ではなく、コレクションに対応するURIを用いて)コレクションの概念に基づきセットアップされているので、collection()要素はXMLデータベース内から呼び出すときに特に役立ちます。eXistデータベースに個々の従業員のコレクションも作成できます(具体的な手順については、ここでは述べません)。そして作成したコレクションを特定のパス(通常はdb/employeesなどの形式)に割り当て、このコレクション内のすべての項目を次のようにして参照できます。
let $employees := collection("/db/employees")
let $employees := collection("xmldb:exist:///db/employees")
xmldb:exist:///は、使用プロトコルがxmldb:で、参照先サーバーがeXistであることを示しています。また、3重のスラッシュ(///)はプロトコルの完全パスの略記法であり、通常は次のような形式になります。
let $employees := collection("xmldb:exist://localhost:8080/db/employees")
フィルタリングフィルタリングとは初期コレクションを絞り込んで重要なレコードだけを処理できるようにすることを意味します。たとえば、特定の部署(たとえば、"Materials")に所属する従業員レコードだけを取り出すものとします。もちろん、この操作は取得プロセスの中で実行できます。
let $employees := collection("/db/employees[division = 'Materials']")
let $employees := collection("/db/employees")
let $filtered-employees := for $employee in $employees[division = 'Materials']" return $employee
let $employees := collection("/db/employees")
let $filtered-employees := for $employee in $employees
where $employee[division = 'Materials']
return $employee
SORT BYが付随するときは、WHERE句を使用した方が通常は効率的で、可読性も高くなります。理屈上は中心となるFLOWRの各演算子があれば何とかなるはずですが、それらの式を組み合わせるだけでは手に負えないこともかなりあります。そのような場合に、IF ... THEN ... ELSE構造を使用します。if ($condExpr) then $resultExprTrue else $resultExprFalse thenとelseにはどちらにも暗黙のreturnが関連付けられており、IF文を用いてかなり複雑なスクリプトを作成できます。たとえば、テーブルに特定セクションの従業員をリストするとき、セクションに従業員がいなければ、ステータスメッセージを表示するものとします。このようなケースでIF...THEN...ELSE文は非常に役立ちます(リスト4を参照)。リスト4 IF文を用いた複雑なスクリプトの作成
let $employees := doc('employees.xml')/employees/employee
let $divisions := ('Materials','AcctsPayable','Operations')
let $results := <html>
<head>
<title>Division Roster</title>
</head>
<body>
{
for $division in $divisions return
if (not(empty($employees[division = $division]))) then
<div>
<h2>{$division}</h2>
<table>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Title</th>
</tr>
{for $employee in $employees[division = $division] order by $employee/lastname ascending return
<tr>
<td>{string($employee/lastname)}</td>
<td>{string($employee/firstname)}</td>
<td>{string($employee/title)}</td>
</tr>
}
</table>
</div>
else <h2>There are no employees in the {string($division)} division.</h2>
}
</body>
</html>
return $results
()と表記)を出力に使用します。if ($cond) then $output else () IF...THEN...ELSE文を結果ブロック内に入れ子にすることもできますが、複数の文を入れ子にすると、式がかなり複雑になることがあります。変数$hの値に基づいて異なるヘッダースタイルを作成することを考えます(この変数は1?6の値を取るものとします)。埋め込みのIF文を使えば、次のようなスイッチを作成できます。
let $title := "This is a test."
let $result := if ($h = 1) then <h1>{$title}</h1>
else if ($h = 2) then <h2>{$title}</h2>
else if ($h = 3) then <h3>{$title}</h3>
else if ($h = 4) then <h4>{$title}</h4>
else if ($h = 5) then <h5>{$title}</h5>
else <h6>{$title}</h6>
return $result
element文を利用して要素を直接作成することもできます。
let $title := "This is a test."
let $result := element {concat("h",$h)}{$title}
element文は、最初の式を新しく作成される要素の名前として扱い、次の括弧内の式を要素のコンテンツとして扱います。このようにXQueryでは、同じ作業をいくつもの異なる方法で実行できます。XQueryは複雑になりがちなので、case値に基づいて特定の出力を得られる次のようなswitch文を使いたいところです。
switch($expr){
case $expr1: $result1
case $expr2: $result2
default: $fallthruResult
}
typeswitch($context)
case $a as type1 return $expr1
case $a as type2 return $expr2
case $a as type3 return $expr3
default $a return $fallthruResult
typeswitch制御構造を使います。
<div>
{for $employee in doc("employees.xml")/employees/employee return
for $node in $employee/*
return typeswitch($node)
case $a as element (firstname) return <span>{string($a)} </span>
case $a as element (lastname) return <span>{string($a)}</span>
case $a as element (title) return <div>{string($a)}</div>
case $a as element (division) return <div><b>{string($a)}</b></div>
default $a return <div>{string($a)}</div>
}
</div>
typeswitchの問題は、言語のエッジケースを解決するのには有効でも、文字列トークンに基づいて異なるパスを選択するような一般的な条件選択には向いていないことです。幸い、多少手を加えることで、typeswitchを従来のswitchに近づけることができます。これはXQueryの式を用いて一時要素にトークンを返すという手法です。これにより、従業員の所属する部門に基づいて、異なる出力を生成できます(リスト5を参照)。リスト5 XQuery式の使用
<div>{
let $employees := doc("employees.xml")/employees/employee
let $output := for $employee in $employees return
let $division := element {string($employee/division)}{}
return typeswitch($division)
case $a as element (Materials) return
<div>
<h1 class="materials_title">Materials Section</h1>
<div class="name">{concat($employee/firstname,' ',$employee/lastname)}</div>
<div class="title">{string($employee/title)}</div>
<div class="manager">{
let $manager := $employees[@id = string($employee/supervisor)]
return concat($manager/firstname,' ',$manager/lastname)
}</div>
</div>
case $a as element (AcctsPayable) return
<div>
<h1 class="acctspayable_title">Accounts Payable</h1>
<div class="name">{concat($employee/firstname,' ',$employee/lastname)}</div>
<div class="title">{string($employee/title)}</div>
<div class="manager">{
let $manager := $employees[@id = string($employee/supervisor)]
return concat($manager/firstname,' ',$manager/lastname)
}</div>
</div>
default return
<div>
<h1>Warning!</h1>
<p>{concat($employee/firstname,' ',$employee/lastname)} is not in a known division</p>
</div>
return $output
}</div>
let $division := element {string($employee/division)}{}
element {string($employee/division)}{}は一見すると暗号のようですが、1つ1つ分解すればすぐ理解できます。最初の{}内の式はMaterialsやAcctsPayableというような部門の名前となります。次の空の{}内の式は要素が空であることを示します。これで<Materials />や<AcctsPayable />という形式の要素が作成されます。この要素から、さまざまなケースに展開していくことができます。次に例を示します。
case $a as element (Materials) return
<div>
<h1 class="materials_title">Materials Section</h1>
<div class="name">{concat($employee/firstname,' ',$employee/lastname)}</div>
<div class="title">{string($employee/title)}</div>
<div class="manager">{
let $manager := $employees[@id = string($employee/supervisor)]
return concat($manager/firstname,' ',$manager/lastname)
}</div>
</div>
$a変数に部門要素のポインタが設定されますが、上の例ではこれは使われておらず、caseルーチンの単なるプレースホルダーとなっています。XQuery 1.1作業草案では、他の制御構造についても言及されています。たとえば、 GROUP BY演算子は、グループ選択子で結果を集約できるようにします。また、WINDOW句は、特定のシーケンスのサブシーケンスに対して集合操作を容易に行えるようにします。さらにXQuery Scripting Extensions(XqueryScript)では、より本格的なスクリプティングロールの中でXQueryを簡単に使えるようにするための制御構造が提供されます。しかし、これらの草案はどちらもまだ開発途上にあり、現在のところ、これらの機能をサポートする商用またはオープンソースのXQuery実装は存在しません。XQueryと制御構造制御構造は必ずしも魅力的なものではありません。実際、それらは鉄筋を支える型枠のようなものですが、建築における型枠がそうであるように、XQueryアプリケーションという構造物の必須の要素でもあります。これらの構造の操作方法を理解するかどうかで、実用的で柔軟性の高いアプリケーションを作成できるか、毎回書き直すしかない1回限りのコードばかりを作成するかが決まってきます。著者紹介Kurt Cagle(Kurt Cagle)
ライター、情報アーキテクト、XML News NetworkとMetaphorical Webのウェブマスター。カナダ、ブリティッシュコロンビア州のビクトリア在住。
関連記事 最新トップニュース
|
japan.internet.com 10周年記念
インターネットコムマーケティングセミナー ROI を最適化するパフォーマンスマーケティングの最前線 【12/16(水)13時〜 東京・赤坂】 申込はコチラ>>
|