読者です 読者をやめる 読者になる 読者になる

M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

PentahoのGet data from XMLステップでツリーをリレーションに分解

Pentaho

直近の関心事項として「あるアプリケーションの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 APIJSON形式だけでなく、XML形式もサポートしているものと都合よく仮定します。この場合、Get data from XMLデータ変換ステップが使えます。このステップではXPathを使ってXMLデータをレコードに変換します。

f:id:m12i:20160528085948p:plain

まずは何はともあれ[ファイル]タブで読み込む対象のXMLファイルを指定します。今回はXPathを使ってXMLデータを変換するという部分にフォーカスしたいので、ファイルパス指定しました:

f:id:m12i:20160528090019p:plain

そのファイルに記載された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」とします:

f:id:m12i:20160528090027p:plain

そして[フィールド]タブのフィールド一覧で最前指定したループ対象のノードをコンテキスト・ノードとみなし、そこから値を取り出すためのXPathを指定します:

f:id:m12i:20160528090034p:plain

「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」に変更します:

f:id:m12i:20160528090053p:plain

続いて[フィールド]タブのフィールド一覧も設定内容を変更します。先ほどは「lv1-list」という単一ノードの子要素「lv2-item」をループ処理しましたが、今度は複数の異なるノードの子要素である「lv3-list」のそのまた子要素である「lv4-item」をループ処理しています:

f:id:m12i:20160528090102p:plain

1つめの例と本質的に異なるところはありません。XPathが指し示すノードであれば親のノードが異なっていようとすべてループ対象となるということです。

こうしてあるXMLデータについて複数のレベルで解析(変換)を行うことで、「ツリーのスキーマ複数のリレーションのスキーマに分解し、結果出来上がったレコードをRDBMSに投入する」という処理が可能になります。

f:id:m12i:20160528093634p:plain