最新のXML仕様を実践で覚える「XQueryチュートリアル」(2)
XQueryのFLWR表現式を使いこなす

XMLの本格利用に向けた重要な技術の1つがXMLデータベースの発展だ。そのカギを握るのは、問い合わせ言語XQueryの標準化である。現在、XQueryは標準化目前のところまできており、実際にXQueryの実装も登場している。本記事は、そのXQueryの実践を目的とした。

戌亥稔
ビーコンIT
2002/12/10



QuiPのバージョンアップ

 第1回「XQueryを実体験してみる」では、QuiP v2.1.1を利用してXQueryを実行したが、2002年8月に独ソフトウェア・エージーが新バージョンのQuiP v2.2.1.1を公開したため、今回からはQuiPのv2.2.1.1を使って解説する。まだQuiP v2.1.1を使っているユーザーは、ソフトウェア・エージー、またはビーコンITのWebサイトから、最新版のQuiPのダウンロードをお勧めする。インストールなどの注意次項は基本的には同じなので割愛する。

 QuiP v2.2.1.1はW3Cが策定中のXQueryの2002年4月30日版のワーキングドラフトに準拠している(2002年12月時点では、2002年11月15日版のワーキングドラフトが最新だ)。

筆者が今回の記事を執筆している間に、W3CはXQueryのワーキングドラフトを2回アップデートしている(2002年8月16日版11月15日版)。11月15日版ではFLWR表現式はFLWOR表現式(同様にフラワーと読む)に変更された。これは従来のFLWR表現式のFor、Let、Where、Returnに、Order byが加えられたものである。しかし、この記事で使用しているQuiP v2.2.1.1は2002年4月30日版のワーキングドラフトに対応しているため、ここではFLWRのまま説明を続ける。

 実は、前回作成した問い合わせの中にはQuiP v2.2.1.1でそのまま実行するとQuiPが暴走するものがある。そこで、QuiP v2.2.1.1で実行する場合は変更が必要である。例えばexample-2-2-2を以下に示す。

for $i in
 document("Tutorial/data/projects.xml")//project[count(
members/member)>=2]
return
<result> <project>{$i/name/text()}</project>
  <members>{$i//member/name}</members>
</result>
[ example-2-2-2] 2名以上メンバーが存在するプロジェクトのプロジェクト名とメンバーの一覧を作る

 この例では、count(members/member)>="2"と、数字にダブルクオーテーションを付けると、QuiP v2.2.1.1でも正しく動作するようになる。ビーコンITからダウンロードできるチュートリアルの例を含んだQuiP v2.2.1.1では、すでにこのような修正をしてあるので、可能ならばそちらをダウンロードすることを勧める。

 QuiPの最新アーカイブファイルのダウンロードは下記から行える。

 では、前回に続きビーコンIT版を前提にXQueryのチュートリアルを進めていこう。

FLWR表現式をネストする

 XQueryの問い合わせ式の1つであるFLWR表現式は、リレーショナルデータベースで用いられている問い合わせ言語SQLのSELECT句、FROM句、WHERE句のように、For、Let、Where、Returnの4つの句からなる、非常に高度で柔軟な演算をXML文書に対して実行できる表現式だ。

 前回は、単純なFLWR表現式を解説してきたが、FLWR表現式では次のように、For句をReturn句の中でネストして記述することで、さまざまなXMLのアウトプットを作り出すことができる。

for $i in
document("Tutorial/data/projects.xml")//project
let $m := $i//member
where count($i//member) >="2"
return
<result> <project>{$i/name/text()}</project>
  { for $n in $m/name
  return <name>{$n/text()}</name> }
</result>
[ example-2-2-5] FLWR表現式で、For句をネストする

 example-2-2-5によって、下記のプロジェクトごとに整理されたXML文書のデータから、2人以上から構成されるプロジェクトだけを抜き出し、その構成員の名前を出力すると、画面7のような結果を取り出せる。

<?xml version="1.0" encoding="UTF-8" ?>
<projects>
  <project code="200200020" start="2002/01/20" end="2002/03/31">
    <name>XMLによる文書管理システム</name>
    <members>
      <member>
        <name>川泉陽一</name>
        <manpower unit="hour">100</manpower>
      </member>
      <member>
        <name>中田聡</name>
        <manpower unit="hour">50</manpower>
      </member>
    </members>
  </project>
  <project code="200200025" start="2002/02/15" end="2002/03/25">
    <name>XMLによるB2Bシステム構築</name>
    <members>
      <member>
        <name>本岡欣也</name>
        <manpower unit="hour">100</manpower>
      </member>
      <member>
        <name>川泉陽一</name>
      <manpower unit="hour">50</manpower>
      </member>
    </members>
  </project>
  <project code="200200031" start="2002/03/15" end="2002/03/31">
    <name>モバイルシステムの構築</name>
    <members>
      <member>
        <name>川泉陽一</name>
        <manpower unit="hour">10</manpower>
      </member>
    </members>
  </project>
</projects>
リスト1(再録) 検索対象となるファイルProjects.xml

画面7 2名以上の構成員からなるプロジェクト

Sort表現式

 FLWR表現式でもPath表現式と同様にSort表現式を使うことによって、繰返し要素を並べ替えることが可能である。

for $i in document("Tutorial/data/projects.xml")//project sortby (name ascending)
let $m := $i//member
where count($i//member) >="2"
return
<result> <project>{$i/name/text()}</project>
  { for $n in $m/name sortby (../name ascending)
  return <name>{$n/text()}</name> }
</result>
[ example-2-2-6] FLWR表現式でSort表現式を使う

画面8 プロジェクト名によってソート

FLWR表現式のコンストラクション機能で別の形式に変換する

 ここまではFLWR表現式の特徴である、結果のコンストラクション(構築)について試してきた。このコンストラクションの特徴を用いて、今度は実際のアプリケーションで必要と思われる問い合わせを発行してみる。

 これまで使ってきたprojects.xmlには、プロジェクトごとにどのようなメンバーが参加しているかが記述されていた。参加メンバーが複数人いる場合には、<members>の下の<member>がオカレンス(繰り返し)を持つ。これはアプリケーションとしてプロジェクト単位で管理する方が都合が良いからである。しかしながら、時には、だれがどのようなプロジェクトに参加しているのかを一覧表示したい場合もある(画面9)。

 これは、従来のXPath式では記述できなかった問い合わせである。画面9は、アプリケーションが実際に獲得したい出力形式のイメージである。

 従来のリレーショナルデータベースでは、格納するデータをすべて2次元の表形式(第1正規化)にするため、プロジェクトとメンバーの関係を持った表を作る必要があった。このおかげで、データをプロジェクトでくくってみるか、それとも構成メンバーでくくってみるか、いずれか2種類の見方(ビュー)を作成できる。

 つまり、リレーショナルデータベースでは、テーブルをジョインしたビューを作成することで、プロジェクトと構成メンバーのどちらを軸としてみるかを切り替えてきたのである。逆にいえば、リレーショナルでは必ずジョインをしなければ、これまで記述してきたような情報を取り出すことができなかった。

 その点XMLでは、プロジェクトとメンバーの関係を階層化して持つのが普通である。リレーショナルでは関係をテープルとして扱うのに対して、XMLでは関係を階層として表すことができるからだ。例えば発注伝票などもこのような形式をしている。

 リレーショナルデータベースにおいては、発注伝票は伝票全体を表すテーブルとその明細を表すテーブルに分割する。明細のテーブルには、伝票と明細との関係(リレーション)が示されている。

 一方XMLにおいては、1つのXML文書で発注伝票を表す。発注と発注明細を表すためには、わざわざ2つのXML文書を作らなくとも、1つの文書の中で階層化すれば十分に表現できるからだ。

画面9 メンバーごとに、どのプロジェクトに所属しているのかを示した結果

 プロジェクトごとに整理されているオリジナルドキュメントの内容を、画面9のようなメンバーごとの形式に変換するには、下記のようなXQueryを発行すればよい。

let $in := document("Tutorial/data/projects.xml")
for $mn in distinct-values($in//member/name)
return
<memberlist>
  {
  <member> {$mn/text()} </member> , <projects> {
    for $p in     
    document("Tutorial/data/projects.xml")//project[members/member/name = $mn]
    return <projectname> { $p/name/text() } </projectname>
  } </projects>
  }
</memberlist>
[ example-2-3-1] メンバーごとに、所属しているプロジェクトを表示する

 example-2-3-1ではFor句がネストされており、外側のFor句にあるdistinct-values関数によって、プロジェクトに参加しているメンバーの一覧を作り出している。そして内側のFor句でそれぞれのメンバーの参加しているプロジェクトを拾い上げて、出力結果を作り出している。

Let句を使い、要素を追加表示

 次にプロジェクト名だけではなく、プロジェクトごとにどれぐらいの工数を計画しているかという、<manpower>の要素を同時に表示してみる。Let句を使い、<manpower>を$mpにバインドする。ここで、プロジェクト名の場合は$p/name/text()とプロジェクト名だけを取り出しているのに対して、$mpの場合は$p/members/member[…]/manpowerと、<manpower>のエレメント全体を取り出していることに注目してほしい。

let $in := document("Tutorial/data/projects.xml")
for $mn in distinct-values($in//member/name)
return
<memberlist>
  {
  <member> {$mn/text()} </member> , <projects> {
    for $p in
     document("Tutorial/data/projects.xml")//project[members/member/name = $mn]
    let $mp := $p/members/member[name = $mn]/manpower
    return <project> <name>{ $p/name/text() }</name> {$mp} </project>
  } </projects>
  }
</memberlist>
[ example-2-3-2] manpower要素を同時に表示する

 この結果では、<manpower unit="hour">50</manpower>のように、オリジナルのドキュメントにあるアトリビュートもそのまま出力される(画面10)。これはW3Cの「XML Query Requirements(XML Queryの要件)」2001年2月15日版の中にあるStructural Preservation(構造維持)を、XQueryが忠実に実行しているのにほかならない。つまり、XQueryは結果のコンストラクション(上記XML Query Requirementsでは、Structural Transformation:構造変換として説明している)だけではなく、従来のXPathが持っていた、構造維持の特徴も持ち合わせているのである。

画面10 manpower要素も同時に出力された結果

ForとLet句が作成するタプル

 ForとLetの特徴を説明するためにもう1点注目してほしい個所がexample-2-3-2にある。以前に公開された記事「標準化目前:注目のXML問い合わせ言語『XQuery』」でも説明したように、ForとLetはタプル(結果の組)を形成する。example-2-3-2ではRetuenの中で記述されている、for $p in document(…)//project[…]と、let $mp:=$p/members/member[…]/manpowerがそれに当たる。$pと$mpはタプルを生成する。

 最新のXQueryワーキングドラフトでは、ForとLetが作成したタプルをTuple Streamとして説明している。具体的には、example-2-3-2で外側のFor句(for $mn in distinct-values($in//member/name))の1回目のループ(川泉陽一のループ)で、内側(Result句の中の)のFor、LetのTupple Streamは、次のようなタプルを生成する。

{
(XMLによる文書管理システム, <manpower unit="hour">100</manpower>),
(XMLによるB2Bシステム構築, <manpower unit="hour">50</manpower>),
(モバイルシステムの構築, <manpower unit="hour">10</manpower>),
(Web Servicesプロジェクト, <manpower unit="hour">20</manpower>)
}

 これは1人目のメンバー川泉陽一の中に出てくる、プロジェクト名と予定工数のコンビネーションにほかならない。2人目の中田聡のケースは次のようになる。

{
(XMLによる文書管理システム, <manpower unit="hour">50</manpower>),
(XMLによるB2Bシステム構築, <manpower unit="day">5</manpower>),
(Web Servicesプロジェクト, <manpower unit="day">5</manpower>)
}

 中田聡の場合は、モバイルシステムの構築のプロジェクトには参加していないため、3つのタプルが生成される。同様に3人目の本岡欣也は、

{
(XMLによるB2Bシステム構築, <manpower unit="hour">100</manpower>)
}

と、タプルを1つ作成する。

For句のフィルタをWhere句に変更

 さてそれでは、example-2-3-2の問い合わせをexample-2-3-3のように変えてみる。

let $in := document("Tutorial/data/projects.xml")
for $mn in distinct-values($in//member/name)
return
<memberlist>
  {
  <member> {$mn/text()} </member> , <projects> {
    for $p in document("Tutorial/data/projects.xml")//project
    let $mp := $p/members/member[name=$mn]/manpower
    where $p/members/member/name = $mn
    return <project> <name>{ $p/name/text() }</name> {$mp} </project>
  } </projects>
  }
</memberlist>
[ example-2-3-3] 下線のFor句のフィルタをWhere句として外に出した

 変更したのは、Resultの中のFor句(for $p indocument(…)//project[…])のフィルタ([…])をWhere句として外に出しただけである。これを実行しても結果は画面10と同様である。しかし、ForとLetによって生成されるTuple Streamは変わる。example-2-3-3のWhere句を取って実行してみれば一目瞭然である。

let $in := document("Tutorial/data/projects.xml")
for $mn in distinct-values($in//member/name)
return
<memberlist>
  {
  <member> {$mn/text()} </member> , <projects> {
    for $p in document("Tutorial/data/projects.xml")//project
    let $mp := $p/members/member[name=$mn]/manpower
    return <project> <name>{ $p/name/text() }</name> {$mp} </project>
  } </projects>
  }
</memberlist>
[ example-2-3-4] Where句をとってみると、タプルは変わる

 2人目の中田聡のタプルが次のように変わる。

{
(XMLによる文書管理システム, <manpower unit="hour">50</manpower>),
(XMLによるB2Bシステム構築, <manpower unit="day">5</manpower>),
(モバイルシステムの構築, 空白),
(Web Servicesプロジェクト, <manpower unit="day">5</manpower>)
}

 同様に3人目の本岡欣也のタプルは次のようになる。

{
(XMLによる文書管理システム,空白),
(XMLによるB2Bシステム構築, <manpower unit="hour">100</manpower>)
(モバイルシステムの構築, 空白),
(Web Servicesプロジェクト, 空白)
}

 これは、「標準化目前:注目のXML問い合わせ言語『XQuery』」の記事で説明したように、For句は直積を取るからこのようになるのである。

 次に、example-2-3-2の問い合わせをexample-2-3-5のように変えてみる。

let $in := document("Tutorial/data/projects.xml")
for $mn in distinct-values($in//member/name)
return
<memberlist>
  {
  <member> {$mn/text()} </member> , <projects> {
    for $p in
     document("Tutorial/data/projects.xml")//project[members/member/name = $mn]
    let $mp := $p/members/member/manpower
    return <project> <name>{ $p/name/text() }</name> {$mp} </project>
  } </projects>
  }
</memberlist>
[ example-2-3-5] 今度はLet句を変更する

 違う点はResultの中のLet句を$mp := $p/members/member/manpowerとフィルタ([…])を取り除いただけである。結果は画面11のように直積を取らずに、それぞれのタプルの後に加えられるだけである。

画面11 let句のフィルタを取る

 この2つの例は、For句とLet句の仕組みを理解しやすいように説明しただけで、実際にはexample-2-3-2またはexample-2-3-3のように記述するのが普通だ。このどちらを記述した方が良いかは実装によって変わるので一概にはいえないが、example-2-3-2のような記述をする方が無難である。

 次回は、 XQueryであらかじめ定義されている関数とユーザー定義関数について解説する予定だ。

参考:これまでに紹介した問い合わせファイルのリスト

Query名 説明
InstallTest.xquery インストールテスト
example-2-1-1.xquery Projects.xmlの中から全てのプロジェクト名を取り出す
example-2-1-2.xquery プロジェクトの終了が遅い順番に並び替える
example-2-1-3.xquery プロジェクトの終了が遅い順番に並び替え、プロジェクト名のみ表示する
example-2-1-4.xquery プロジェクト名でソートし、プロジェクト名のみ表示する
example-2-2-1.xquery 2名以上メンバーが存在するプロジェクトを表示する
example-2-2-2.xquery 2名以上メンバーが存在するプロジェクトのプロジェクト名とメンバーの一覧を作る
example-2-2-3.xquery 2名以上メンバーが存在するプロジェクトのプロジェクト名とメンバーの一覧をつくる。但し、プロジェクト名は属性として作成する
example-2-2-4.xquery FLWR表現のWhere句を使い、2名以上メンバーが存在するプロジェクトのプロジェクト名とメンバーの一覧をつくる
example-2-2-5.xquery FLWR表現をネストし、2名以上メンバーが存在するプロジェクトのプロジェクト名とメンバーの一覧を作る
example-2-2-6.xquery FLWR表現をネストし、2名以上メンバーが存在するプロジェクトのプロジェクト名とメンバーの一覧をつくり、プロジェクト名でソートする
example-2-3-1.xquery projects.xmlをメンバー毎にどういうプロジェクトに参加しているかを表すメンバーリストを作成する
example-2-3-2.xquery メンバーリストにそれぞれのプロジェクト毎の計画(フィルタを使う)
example-2-3-3.xquery メンバーリストにそれぞれのプロジェクト毎の計画(Where文を使う
example-2-3-4.xquery プロジェクト計画の入ったメンバーリストのWhere句を取り除き、Tuple streamを確認する。これによりFor句がどのような働きをするか確認する
example-2-3-5.xquery プロジェクト計画の入ったメンバーリストのLet句のフィルタを取り除いて、let句がどのような働きをしているかを確かめてみる

2/5

Index
連載:XQueryチュートリアル
  XQueryを実体験してみる
XQueryのFLWR表現式を使いこなす
  XQueryの関数を使う、定義する
  XQueryによるXML文書の結合〜1
  XQueryによるXML文書の結合〜2


XML & SOA フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

HTML5+UX 記事ランキング

本日月間