PentahoのGet data from XMLステップでツリーをリレーションに分解
直近の関心事項として「あるアプリケーションのREST APIからGETしたリソース(JSON形式であれXML形式であれ)のツリー構造を、いったんRDBMSのリレーションに分解し、その状態でDWHに格納したり、また別のアプリケーションにREST APIでPOST/PUTしたい」というのがありました。そのためのレシピ──とくに上記「関心」のうちリソースをGETするところ──を調べてみたメモです。
JSON Inputの限界
Pentahoには前回記事で紹介した「JSON Input」というデータ変換ステップがありますが、このステップの欠点はツリー構造をリレーションに分解するには貧弱だということです。このステップはJSONPathというDSLで宣言的にJSON情報をレコードの情報に変換できますが、「○○の親ノードの××」というようなツリー構造のルートに向かう探索ができないため、例えば下記のような(ブログ記事のリストをイメージした)JSONにおいて、「記事のIDとそれに紐づくタグ名」(記事:タグ=N:M)というリレーションを抽出できないのです:
{ "page": 1, "perPage": 25, "total": 500, "list": [ { "id": 123, "title": "item 123", "tags": ["foo", "bar", "baz"], "content": "..." }, { "id": 124, "title": "item 124", "tags": ["foo", "bar"], "content": "..." }, { "id": 122, "title": "item 122", "tags": [], "content": "..." } ] }
Get data from XMLの導入
そこで代替案です。アプリケーションのREST APIはJSON形式だけでなく、XML形式もサポートしているものと都合よく仮定します。この場合、Get data from XMLデータ変換ステップが使えます。このステップではXPathを使ってXMLデータをレコードに変換します。
まずは何はともあれ[ファイル]タブで読み込む対象のXMLファイルを指定します。今回はXPathを使ってXMLデータを変換するという部分にフォーカスしたいので、ファイルパス指定しました:
そのファイルに記載されたXMLデータは次のようなものです:
<root> <lv1-item id="1">Lv1 content [0]</lv1-item> <lv1-item id="2">Lv1 content [1]</lv1-item> <lv1-list id="3"> <!-- loop XPath example #1--> <lv2-item id="4">Lv2 content [0]</lv2-item> <lv2-item id="5">Lv2 content [0] <lv3-item id="6">Lv3 content [0]</lv3-item> <lv3-list id="7"> <!-- loop XPath example #2--> <lv4-item id="8">Lv4 content[0]</lv4-item> <lv4-item id="9">Lv4 content[1]</lv4-item> <lv4-item id="10">Lv4 content[2]</lv4-item> </lv3-list> </lv2-item> <lv2-item id="11">Lv2 content [0] <lv3-item id="12">Lv3 content [0]</lv3-item> <lv3-list id="13"> <!-- loop XPath example #2--> <lv4-item id="14">Lv4 content[0]</lv4-item> <lv4-item id="15">Lv4 content[1]</lv4-item> </lv3-list> </lv2-item> <lv2-item id="16">Lv2 content [0] <lv3-item id="17">Lv3 content [0]</lv3-item> <lv3-list id="18"> <!-- loop XPath example #2--> </lv3-list> </lv2-item> </lv1-list> </root>
まずはコメントで「loop XPath example #1」と記述したレベルのノードをレコードに変換する例です。[全般]タブの[ループXPath]欄にレコードに変換するためのループ処理のターゲットになるノードをXPathで指定します。ループ対象のノードであって、ループ対象のノードの親ノード(コンテナになるノード)ではありません。なので「/root/lv1-list/lv2-item」とします:
そして[フィールド]タブのフィールド一覧で最前指定したループ対象のノードをコンテキスト・ノードとみなし、そこから値を取り出すためのXPathを指定します:
「lv2-item_id」フィールドと「lv2-item_id_asnode」フィールドの例で示したとおり、[要素タイプ]列では「ノード」もしくは「属性」が選べて、後者の場合属性値を取得するのに「@…」記法を省略できるようですが、それだけのことなので、あまり有用性を感じません。。
重要なのは「lv1-list_id」フィールドと「lv1-item_id1」フィールドの例です。XPathでコンテキスト・ノード(=ループ対象ノード)を起点にルートの方向に探索を行っています。この機能のおかげで「記事のIDとそれに紐づくタグ名」(記事:タグ=N:M)とか「チケットIDとそれに紐づくウォッチャーのID」(チケット:ウォッチャー=N:M)といったリレーションを抜き出すことが可能になります。
今度はより深いレベル──「loop XPath example #2」というコメントを記述したレベルのノードをレコードに変換する例です。[全般]タブの[ループXPath]欄を「/root/lv1-list/lv2-item/lv3-list/lv4-item」に変更します:
続いて[フィールド]タブのフィールド一覧も設定内容を変更します。先ほどは「lv1-list」という単一ノードの子要素「lv2-item」をループ処理しましたが、今度は複数の異なるノードの子要素である「lv3-list」のそのまた子要素である「lv4-item」をループ処理しています:
1つめの例と本質的に異なるところはありません。XPathが指し示すノードであれば親のノードが異なっていようとすべてループ対象となるということです。
こうしてあるXMLデータについて複数のレベルで解析(変換)を行うことで、「ツリーのスキーマを複数のリレーションのスキーマに分解し、結果出来上がったレコードをRDBMSに投入する」という処理が可能になります。