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")
XQuery for Java(xqj)表記では次のようになります。
let $employees := collection("xmldb:exist:///db/employees")
ここで、xmldb:exist:///は、使用プロトコルがxmldb:で、参照先サーバーがeXistであることを示しています。また、3重のスラッシュ(///)はプロトコルの完全パスの略記法であり、通常は次のような形式になります。
let $employees := collection("xmldb:exist://localhost:8080/db/employees")
内部的には、取得されたコレクションは少なくともクエリに関する限り、シーケンスとして扱われます(本稿の範囲を超えますが、アップデートでは区別されます)。つまり、コレクションの結果に対してクエリを実行するときも、ドキュメントから取り出したXPathシーケンスの結果に対してクエリを実行するときも、コンテンツを同じ方法で操作することになります。
