mb_language("uni"); mb_internal_encoding("utf-8"); mb_http_input("auto"); mb_http_output("utf-8"); ?>
2005年3月4日 更新
XprioriおよびNeoCore(以下便宜上Xprioriと総称)を使うのは決して難しくないが、SQLデータベースとは異なるノウハウが要求される。今回は、Xprioriでクエリを実行する際に知っておくべきTipsを紹介する。
XQueryのif-then-elseは、クエリの条件によって返す式を切り替えるには便利である。しかし、残念ながらXprioriのXQueryにはまだ実装されていない。
すべてのケースで有効というわけではないが、目的のノードを選択する式を工夫することで、意図する結果が得られる場合がある。以下のXML文書を例に説明する。
<?xml version="1.0"?> <価格情報> <商品コード>000123</商品コード> <価格タイプ>1</価格タイプ> <価格1>98</価格1> <価格2>110</価格2> </価格情報>
ここから、商品コードが000123の価格情報要素に含まれる価格を問い合わせたいとする。ただし、価格タイプの値が1の場合は価格1要素を、そうではないときは価格2要素を得たい場合、どうすればよいか。
if-then-elseを使わずとも、1回のクエリで意図どおりの結果を得ることができる。具体的には、以下のようにクエリを記述する。
/ND/価格1[(../商品コード = "000123" and ../価格タイプ = "1")]|/ND/価格2[(../商品コード = "000123" and ../価格タイプ != "1")]
このクエリは、2つの式を「|」で連結している。この2つは、絶対に同時に成立することがない条件「(../価格タイプについての式)」を含んでいるので、結果となる要素は常に1つである。
FLWR文のFとLは、forとletの頭文字である。この2つは、使い方によっては同じ結果を導き出すため紛らわしい。以下のXML文書を例に説明する。
<ユーザーリスト> <ユーザー>田中</ユーザー> <ユーザー>佐藤</ユーザー> <ユーザー>鈴木</ユーザー> </ユーザーリスト>
このXML文書に、forとletを使った2つのクエリを実行してみる。
for $i in /ND/ユーザー return $i
let $i := /ND/ユーザー return $i
結果は、いずれも以下のようになる。
<?xml version="1.0" encoding="UTF-8" ?> <Query-Results> <ユーザー>田中</ユーザー> <ユーザー>佐藤</ユーザー> <ユーザー>鈴木</ユーザー> </Query-Results>
この結果をもって、forとletは同じ処理を行うこともできると考えるのは早計である。上記の例は、結果として同じものが得られているだけで、実際の処理内容は同じではない。forとletの違いは、クエリのreturn句に「"!"」を付加すると明確になる。
for $i in /ND/ユーザー return ($i , "!")
このクエリの結果は以下のようになる。
<?xml version="1.0" encoding="UTF-8" ?> <Query-Results> <ユーザー>田中</ユーザー> ! <ユーザー>佐藤</ユーザー> ! <ユーザー>鈴木</ユーザー> ! </Query-Results>
「!」が3つあるのは、return句が3回処理されたことを意味する。つまり、変数 $i には1つのユーザー要素が3回格納されたのだ。これがforの機能である。forは、in以降の式の値を分解して、1つ1つ独立した処理を可能にする。
一方、letの場合はどうか。
let $i := /ND/ユーザー return ($i , "!")
このクエリの結果は以下のようになる。
<?xml version="1.0" encoding="UTF-8" ?> <Query-Results> <ユーザー>田中</ユーザー> <ユーザー>佐藤</ユーザー> <ユーザー>鈴木</ユーザー> ! </Query-Results>
見てのとおり、「!」は1つしかない。つまり、return句は1回しか処理されていない。変数 i には3つのユーザー要素がまとめて代入されていて、1回のreturn句でそれを結果として出力していたわけである。
このように、forとletは結果が同じでも内部処理はまったく異なっている。分解することなくまとめて扱う場合はlet、分解して個別の処理を行う場合はforを使うべきである。
FLWR文で使用するforやletは、ネストすることができる。その際の処理順序を実例を使ってみてみよう。テストデータとして以下のXML文書を用意した。
<?xml version="1.0" encoding="Shift_JIS"?> <天気予報> <予報><地名>東京</地名><天候>快晴</天候></予報> <予報><地名>大阪</地名><天候>曇天</天候></予報> <予報><地名>名古屋</地名><天候>雨天</天候></予報> </天気予報>
このテストデータに対して、for句を2つ持つ以下のクエリを実行する。
for $地名 in /ND/地名 for $天候 in /ND/天候 return <結果>{($地名,$天候)}</結果>
このクエリは、最初のfor句で地名要素の数(3)だけ繰り返す。さらに、2番目のfor句で天候要素の数(3)だけ繰り返す。つまり、3回の繰り返しが3回発生するので、結果要素は3×3=9個得られることになる。
問題は、この9個の結果がどのような順番で得られるかである。実際にクエリを行った結果は以下のようになる。まず、最初の地名が固定された状態で、3種類の天候が繰り返されている。つまり、1番目のfor句が最初に処理され、2番目のfor句は1番目のfor句の中で処理されているのである。
<?xml version="1.0" encoding="UTF-8" ?> <Query-Results> <結果> <地名>東京</地名> <天候>快晴</天候> </結果> <結果> <地名>東京</地名> <天候>曇天</天候> </結果> <結果> <地名>東京</地名> <天候>雨天</天候> </結果> <結果> <地名>大阪</地名> <天候>快晴</天候> </結果> (以下略)
余談だが、for句によって選択されたノードから、親ノードをたどることもできる。例えば、以下のように地名をfor句で列挙させつつ、その親ノードの子要素(../天候)を出力させることもできる。
for $地名 in /ND/地名 return<結果>{($地名,$地名/../天候)}</結果>
この結果は以下のようになり、それぞれの地名要素に対応する天候要素(親ノードの子要素)が得られることが分かるだろう。
<?xml version="1.0" encoding="UTF-8" ?> <Query-Results> <結果> <地名>東京</地名> <天候>快晴</天候> </結果> <結果> <地名>大阪</地名> <天候>曇天</天候> </結果> <結果> <地名>名古屋</地名> <天候>雨天</天候> </結果> </Query-Results>
position()を使うと、ノード群の何番目かという情報を得ることができる。不等号と組み合わせれば、最初から指定個数だけを切り出すことも容易である。以下のXML文書を例に説明する。
<?xml version="1.0" encoding="Shift_JIS"?> <ユーザー名前リスト> <ユーザー><名前>田中</名前></ユーザー> <ユーザー><名前>佐藤</名前></ユーザー> <ユーザー><名前>鈴木</名前></ユーザー> </ユーザー名前リスト>
ユーザー名前リストから最初の2つの名前要素だけを取り出すには、どのような式を記述すればよいだろうか。
/ND/ユーザー名前リスト/ユーザー[position() < 3]
このクエリの結果には、以下のように必要としていないユーザー要素が含まれてしまう。
<ユーザー> <名前>田中</名前> </ユーザー> <ユーザー> <名前>佐藤</名前> </ユーザー>
取り出したいのは名前要素であるから、「[position() < 3]」によって限定する対象は、「/ND/ユーザー名前リスト/ユーザー」ではなく「/ND/ユーザー名前リスト/ユーザー/名前」でなければならない。そこでクエリを以下のように書き換えてみる。
/ND/ユーザー名前リスト/ユーザー/名前[position() < 3]
このクエリでは、3つ目の鈴木という名前まで出てきてしまう。
<名前>田中</名前> <名前>佐藤</名前> <名前>鈴木</名前>
このような結果になるのは、「[position() < 3]」という式が限定するのが、その直前の名前要素そのものだからである。個々の名前要素はユーザー要素の最初の子ノードであるため、position()の値はすべて「1」になる。そのため、あらゆる名前要素は「position() < 3」という条件を満たしてしまう。
では、「XML文書全体で最初の2つだけ」という制約はできないのだろうか。それは可能である。以下のように括弧を付け足せばよい。
(/ND/ユーザー名前リスト/ユーザー/名前)[position() < 3]
「(/ND/ユーザー名前リスト/ユーザー/名前)」という部分は、3つの名前要素を持つノード集合を示す式である。「[position() < 3]」は、そのノード集合を制限するので、3つの名前要素の中の2つだけが得られることになる。
<名前>田中</名前> <名前>佐藤</名前>
任意文字列にマッチさせるために、アスタリスク演算子を使うことができる。例えば、以下のクエリは名前要素の内容が田中でも田辺でもマッチする。
/ND/ユーザー[名前="田"*]
しかし、アスタリスク演算子を引用符の中に入れてしまうと、田中、田辺いずれもマッチしなくなってしまう。
/ND/ユーザー[名前="田*"]
なぜなら、引用符内の「*」記号は一般の文字扱いとなり、もはや演算子ではなくなってしまうからである。逆に、演算子に当たる文字を一般の文字として使いたい場合は、引用符の中に置けばよいということになる。使い方に十分注意しよう。
式の書き方によって、意味は同じでも処理速度に差が出る場合がある。例えば以下の2つがその例である。
/ND/hoge/foo/bar/com[../../../MetaData/DocID="12"] /ND[MetaData/DocID="12"]/hoge/foo/bar/com
MetaDataはXMLドキュメントを格納すると作成されるメタ情報、検索式はDocIDが12のデータの/hoge/foo/bar/comのデータ取得を要求している。どちらも同じ意味だが、後者の方が高速となる。速度差が生じるのは、XprioriのXMLデータの格納方法に起因する。
速度を稼ぎたい場合は、同じ意味を持つ複数の式を試してみると改善策が見つかるかもしれない。
▲このページのTOPへ