フィルタリング
フィルタリングとは初期コレクションを絞り込んで重要なレコードだけを処理できるようにすることを意味します。たとえば、特定の部署(たとえば、"Materials")に所属する従業員レコードだけを取り出すものとします。もちろん、この操作は取得プロセスの中で実行できます。
let $employees := collection("/db/employees[division = 'Materials']")
一方、これを2つの別のステップに分けると、設計とパフォーマンスの両面でメリットがあります。
let $employees := collection("/db/employees")
let $filtered-employees := for $employee in $employees[division = 'Materials']" return $employee
XQuery WHEREコマンドも使用でき、これで評価の述部が分離されます。
let $employees := collection("/db/employees")
let $filtered-employees := for $employee in $employees
where $employee[division = 'Materials']
return $employee
どちらが有利でしょうか? 述部([]内の式)が比較的小さく、自己完結的なときは、シーケンスでXPathを使用した方が通常は高速です。しかし、式が複雑なとき、複数の変数が含まれているとき、あるいは、この操作にSORT BYが付随するときは、WHERE句を使用した方が通常は効率的で、可読性も高くなります。理屈上は中心となるFLOWRの各演算子があれば何とかなるはずですが、それらの式を組み合わせるだけでは手に負えないこともかなりあります。そのような場合に、IF ... THEN ... ELSE構造を使用します。
if ($condExpr) then $resultExprTrue else $resultExprFalse
この文のthenとelseにはどちらにも暗黙のreturnが関連付けられており、IF文を用いてかなり複雑なスクリプトを作成できます。たとえば、テーブルに特定セクションの従業員をリストするとき、セクションに従業員がいなければ、ステータスメッセージを表示するものとします。このようなケースでIF...THEN...ELSE文は非常に役立ちます(リスト4を参照)。
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
}
しかし、XQueryには単純なスイッチはありません。XQueryに用意されているのは型スイッチであり、特定の変数のデータ型に基づいてアクションを実行できます。型スイッチのアイデアは、当初、XMLドキュメント内の要素を識別し、その要素に基づき何らかの処理を行う手段として考え出されました。
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を参照)。
<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>
このルーチンで重要なのは次の1行です。
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>
この出力は資材(material)の種類に応じて適切な形式に変換されます。一致するものがないデフォルトのケースでは、特定の従業員レコードに問題があることを示す警告メッセージが発せられます。このケースでは$a変数に部門要素のポインタが設定されますが、上の例ではこれは使われておらず、caseルーチンの単なるプレースホルダーとなっています。
XQuery 1.1作業草案では、他の制御構造についても言及されています。たとえば、GROUP BY演算子は、グループ選択子で結果を集約できるようにします。また、WINDOW句は、特定のシーケンスのサブシーケンスに対して集合操作を容易に行えるようにします。さらにXQuery Scripting Extensions(XqueryScript)では、より本格的なスクリプティングロールの中でXQueryを簡単に使えるようにするための制御構造が提供されます。しかし、これらの草案はどちらもまだ開発途上にあり、現在のところ、これらの機能をサポートする商用またはオープンソースのXQuery実装は存在しません。
XQueryと制御構造
制御構造は必ずしも魅力的なものではありません。実際、それらは鉄筋を支える型枠のようなものですが、建築における型枠がそうであるように、XQueryアプリケーションという構造物の必須の要素でもあります。これらの構造の操作方法を理解するかどうかで、実用的で柔軟性の高いアプリケーションを作成できるか、毎回書き直すしかない1回限りのコードばかりを作成するかが決まってきます。
