Viper 2で学ぶXMLデータベース最新事情
Viper 2で学ぶXMLデータベース最新事情(2)

マッシュアップしたいけどPHPでDOMはイヤ!


日本アイ・ビー・エム
中林 紀彦
2007/11/22

やっぱりXMLにはXMLデータベースとXQueryが素直でいいね

 では今回も、XMLデータベース(XQuery)を使っていたらどうなったのかを考えていきましょう。

 まずはテーブル設計ですが、前回のユーザー管理テーブルと同様に掲示板に関するテーブルもID列(INTEGER型)とXMLDOC列(XML型)の2列のテーブルで、図4のようにデータが入っています(最後に作成用のスクリプトを載せてあります)。

図4 掲示板に関するテーブル

 また、ユーザー管理テーブルについては、テーブル・スキーマ(定義)は前回のままなのですが、XML列が非常に柔軟なので、“好きな食べ物”や“旅行”などの情報が増えています(図5)。

図5 ユーザ管理テーブルに興味のある分野が追加されている

 さて3つのXMLドキュメント(ユーザ情報、掲示板、全国の地点定義表)を組み合わせていよいよマッシュアップですが、XQueryを使えばとても簡単です(リスト6)。XQuery自体は非常にシンプルで、ごく典型的なものです。それぞれのXMLドキュメントから、

  1. 繰り返しになっている要素(ユーザー情報、記事、city)をfor節で繰り返し取り出す
  2. 結合の条件(ユーザーID=投稿者ID、地域=title)を満たす要素をwhere節で絞り込む
  3. 必要な要素や属性だけをreturn節で返す

というFLOWR(フラワー)構文の典型的な例です。また、参考までにDOMでは苦手だった要素の並べ替えを、order by節を使って地域id順にソートしています(ここでもNamespaceは注意してくださいね)。

$sql = <<< XQUERY
  xquery 
  declare namespace IdWeather = 
    "http://weather.livedoor.com/ns/rss/2.0";
  <result>{
  for \$u in db2-fn:xmlcolumn("XMLUSER.XMLDOC")/ユーザ情報
  for \$f in db2-fn:xmlcolumn("XMLFORUM.XMLDOC")/記事
  for \$m in ({$xmldoc})/channel/IdWeather:source/area/pref/city
  where \$u/ユーザ名/@ユーザID = \$f/投稿者ID/text()
  and \$u/地域/text() = \$m/@title
  order by xs:int(\$m/@id)
  return 
  <記事>
    {\$f/@記事ID}
    {\$f/タイトル}
    <投稿者>
      {\$u/ユーザ名}
      <地域>{\$m/@id}{\$u/地域/text()}</地域>
    </投稿者>
  </記事>
  }</result>
XQUERY;
リスト6 記事一覧と全国の地点定義表(RSS)をマッシュアップするXQuery
$xmldocには全国の地点定義表(RSS)が入っています。

 XQueryの詳しい解説などは、

を参考にしてください。

時間が余ったので、追加のマッシュアップ

 さて、XMLデータベースとXQueryを使うことで非常に効率良い開発ができました。この分では時間に余裕ができそうなので、企画と相談し、もう1つマッシュアップを加えることにしました。ユーザー情報には住んでいる“地域”と“好きな食べ物”が入っていますので、リクルートの「ホットペッパーWebサービス」とマッシュアップして、“おすすめのお店”の情報を提供することになりました。

 実装は、お天気のときとほとんど同じだったので、なんと、2時間で機能追加できてしまいました。

$sql = <<< XQUERY
  xquery 
  declare namespace IdWeather =
    "http://weather.livedoor.com/ns/rss/2.0";
  declare namespace Hp =
    "http://webservice.recruit.co.jp/HotPepper/";
  for \$u in db2-fn:xmlcolumn("XMLUSER.XMLDOC")/ユーザ情報
  where \$u/ユーザ名/@ユーザID = "10"
  return <result> {\$u}
    <おすすめのお店 xmlns="http://webservice.recruit.co.jp/HotPepper/">{
    for \$r in ({$xmldoc})/Hp:shop
    return 
      <shop>{\$r/Hp:id}{\$r/Hp:name}</shop>
    }</おすすめのお店>
  </result>
XQUERY;
リスト6 リクルートのホットペッパーWebサービスとマッシュアップするXQuery
$xmldocは、グルメサーチAPIの住所[address]に“地域”を、キーワード[keyword]に“好きな食べ物”を指定してXMLを取得しています。

<?xml version="1.0" encoding="UTF-8" ?>
<result>
  <ユーザ情報>
    <ユーザ名 ユーザID="10">Kさん</ユーザ名>
    <地域>横浜</地域>
    <興味>車</興味>
    <好きな食べ物>ビール</好きな食べ物>
  </ユーザ情報>
  <おすすめのお店 xmlns="http://webservice.recruit.co.jp/HotPepper/">
    <shop>
      <id>J000015153</id>
      <name>横浜 天下鳥 横浜駅西口店</name>
    </shop>
    <shop>
      <id>J000019524</id>
      <name>七輪焼肉 安安 横浜北口店 </name>
    </shop>
    <shop>
      <id>J000019519</id>
      <name>七輪焼肉 安安 横浜西口店</name>
    </shop>
    <shop>
      <id>J000057164</id>
      <name>沖縄家庭料理と泡盛 島ぬ風 相鉄横浜駅前</name>
    </shop>
    <shop>
      <id>J000651963</id>
      <name>和中居酒屋 よっちゃれい 鶴屋町店(ヨッチャレイ ツルヤチョウテン)</name>
    </shop>
    <shop>
      <id>J000015193</id>
      <name>ニュートーキョー ニューホッペン</name>
    </shop>
    <shop>
      <id>J000023209</id>
      <name>キリンビアレストラン鶴見</name>
    </shop>
    <shop>
      <id>J000023181</id>
      <name>和中居酒屋 よっちゃれい 南幸店(ヨッチャレイ ミナミサイワイテン)</name>
    </shop>
    <shop>
      <id>J000064739</id>
      <name>関内82ALE House</name>
    </shop>
    <shop>
      <id>J000015125</id>
      <name>居酒屋 かもん 伊勢佐木町店</name>
    </shop>
  </おすすめのお店>
</result>
リスト7 リスト6から生成されるXMLの例
編集部注:このXMLドキュメントは、リクルートのホットペッパーWebサービスを利用して執筆時点の情報を生成したものであり、最新の店舗情報とは異なる可能性があります。

 いかがでしょうか? XQueryを使うとこのペースでどんどん新しい機能をマッシュアップできそうです。

  さて、第2回は第1回に引き続いて“Kさん”に登場いただきPHPを例にして、XMLデータベースとXQueryを使うことでXMLを扱うプログラムが非常に効率良く、しかも簡単に作成できることをご紹介しました。またマッシュアップを例に、XQueryがXMLデータベースだけの枠にとどまらず、Webサービスで取得できるようなXMLドキュメントを対象にし、プログラム(DOM)での実装が非常に難しかったXMLドキュメントの検索や絞り込み、並べ替えなどが簡単にできることも理解していただけたのではないでしょうか。

 皆さんも、XMLデータベース+XQueryの持つポテンシャルと柔軟さを感じていただけたでしょうか? さて次回も引き続きXQueryをテーマに、XMLデータベースの面白さをご紹介していきたいと思います。(次回へ続く)

データベース作成用のスクリプト


-- データベースの作成
create database xmlsampl using codeset UTF-8 territory JP;
-- テーブルの作成
connect to xmlsampl;
create table xmluser(id int, xmldoc xml);
create table xmlforum(id int, xmldoc xml);
-- データの挿入
insert into xmluser values (1, '<ユーザ情報><ユーザ名 ユーザID="1">Nさん</ユーザ名><地域>富山</地域><興味>映画</興味><好きな食べ物>ビール</好きな食べ物><旅行>北海道</旅行></ユーザ情報>');
insert into xmluser values (2, '<ユーザ情報><ユーザ名 ユーザID="2">Mさん</ユーザ名><地域>東京</地域></ユーザ情報>');
insert into xmluser values (3, '<ユーザ情報><ユーザ名 ユーザID="3">Bさん</ユーザ名><地域>横浜</地域></ユーザ情報>');
insert into xmluser values (4, '<ユーザ情報><ユーザ名 ユーザID="4">Cさん</ユーザ名><地域>さいたま</地域></ユーザ情報>');
insert into xmluser values (5, '<ユーザ情報><ユーザ名 ユーザID="5">Aさん</ユーザ名><地域>横浜</地域></ユーザ情報>');
insert into xmluser values (6, '<ユーザ情報><ユーザ名 ユーザID="6">Eさん</ユーザ名><地域>札幌</地域></ユーザ情報>');
insert into xmluser values (7, '<ユーザ情報><ユーザ名 ユーザID="7">Yさん</ユーザ名><地域>金沢</地域></ユーザ情報>');
insert into xmluser values (8, '<ユーザ情報><ユーザ名 ユーザID="8">Oさん</ユーザ名><地域>横浜</地域></ユーザ情報>');
insert into xmluser values (9, '<ユーザ情報><ユーザ名 ユーザID="9">Fさん</ユーザ名><地域>福岡</地域></ユーザ情報>');
insert into xmluser values (10, '<ユーザ情報><ユーザ名 ユーザID="10">Kさん</ユーザ名><地域>横浜</地域><興味>車</興味><好きな食べ物>ビール</好きな食べ物></ユーザ情報>');
--
insert into xmlforum values (1, '<記事 記事ID="1"><タイトル>PHPモバイルでのドロップダウン</タイトル><更新日>2007-11-11</更新日><投稿者ID>1</投稿者ID></記事>');
insert into xmlforum values (2, '<記事 記事ID="2"><タイトル>javascriptの文字列操作ライブラリ</タイトル><更新日>2007-11-11</更新日><投稿者ID>2</投稿者ID></記事>');
insert into xmlforum values (3, '<記事 記事ID="3"><タイトル>DB2 9.5 ルールベース コストベース</タイトル><更新日>2007-11-11</更新日><投稿者ID>3</投稿者ID></記事>');
insert into xmlforum values (4, '<記事 記事ID="4"><タイトル>変数の宣言とnew</タイトル><更新日>2007-11-11</更新日><投稿者ID>4</投稿者ID></記事>');
insert into xmlforum values (5, '<記事 記事ID="5"><タイトル>DB2 9.5 トリガを仕掛けたところテーブル更新…</タイトル><更新日>2007-11-11</更新日><投稿者ID>5</投稿者ID></記事>');
insert into xmlforum values (6, '<記事 記事ID="6"><タイトル>JNIにおける基本型の二次元配列について</タイトル><更新日>2007-11-11</更新日><投稿者ID>6</投稿者ID></記事>');
insert into xmlforum values (7, '<記事 記事ID="7"><タイトル>AOPを用いての呼び出し元のクラス名の取得につ…</タイトル><更新日>2007-11-11</更新日><投稿者ID>7</投稿者ID></記事>');
insert into xmlforum values (8, '<記事 記事ID="8"><タイトル>動的配列の中のbyte型</タイトル><更新日>2007-11-11</更新日><投稿者ID>8</投稿者ID>    </記事>');
insert into xmlforum values (9, '<記事 記事ID="9"><タイトル>配列を検索するためのユーティリティ</タイトル><更新日>2007-11-11</更新日><投稿者ID>9</投稿者ID></記事>');
insert into xmlforum values (10, '<記事 記事ID="10"><タイトル>テーブルの頻繁な設計変更を楽にしたい</タイトル><更新日>2007-11-11</更新日><投稿者ID>10</投稿者ID></記事>');
--

問題の解答(リスト4の正解)

正解は以下のとおりです。名前空間(Namespace)を指定していませんでした。PHPのDOMに限らず、XQueryでもこのNamespaceの指定は重要ですので、いつも気にかけておいてください。

$xpath = new DOMXPath($dom);
$xpath->registerNamespace(
  'ldWeather', 'http://weather.livedoor.com/ns/rss/2.0');

$query =
  '/rss/channel/ldWeather:source/area/pref/city[@title = "仙台"]';
$results = $xpath->query($query, $dom);
foreach ($results as $result) {
  echo $result->getAttribute('id');
}

  3/3  

 Index
連載:Viper 2で学ぶXMLデータベース最新事情(2)
 マッシュアップしたいけどPHPでDOMはイヤ!
  Page 1
・“マッシュアップ”の追加にKさんの苦悩は続く
・課題1:データベース(MySQL)から取り出した結果セットのXML化
  Page 2
・課題2:DOMを使ったXMLデータの検索
・課題3:XMLデータの並べ替え(ソート)や集計
Page 3
・やっぱりXMLにはXMLデータベースとXQueryが素直でいいね
・時間が余ったので、追加のマッシュアップ


Viper 2で学ぶXMLデータベース最新事情



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

注目のテーマ

Database Expert 記事ランキング

本日月間