特定のルート要素名と名前空間をもつXMLを特定する

要素名や名前空間を条件にしたいとき

xml型に任意のスキーマを持つ(ルート要素も異なる)文書を放り込むような使い方をする場合には、ルート要素が何かというのを特定しなくちゃいけないが、要素名そのものや名前空間を条件にするのは、ちょっと面倒だったりする。
PostgreSQLxml型にXMLを放り込んでxpath()で検索というのはよく行うけれど、そういう場合って要素の値や属性値を条件にすることが多い。

やり方

要素名や名前空間そのものは、xpath関数のlocal-name()でローカル名(要素名)を、namespace-uri()で名前空間URIを取得できるんだけど、これをXPathの述語として与え、その評価結果が空値でないかで判断しなくちゃならない。
例によって、xpath()がxml型配列を返却するから、それをtext型配列にcastして、その先頭要素を取り出してリテラルと比較する。xpathの述語が真なら空値じゃない値が返却されるので、それをWHERE句に記述することになる。書いているだけでも面倒くさい・・・。

xmldb=# \d xml_test
Table "public.xml_test"
Column | Type | Modifiers

                                                    • -

data | xml |

xmldb=# INSERT INTO xml_test VALUES ('<r:root xmlns:r="http://hoge.hoge"><a><b>B1</b><c>C1</c></a></r:root>');
INSERT 0 1
xmldb=# INSERT INTO xml_test VALUES ('<r:nuko xmlns:r="http://hoge.hoge"><a><b>B1</b><c>C1</c></a></r:nuko>');
INSERT 0 1
xmldb=# INSERT INTO xml_test VALUES ('<r:root xmlns:r="http://foo.bar"><a><b>B1</b><c>C1</c></a></r:root>');
INSERT 0 1
xmldb=# select * from xml_test;
data

                                                                                                                                            • -

<r:root xmlns:r="http://hoge.hoge"><a><b>B1</b><c>C1</c></a></r:root>
<r:nuko xmlns:r="http://hoge.hoge"><a><b>B1</b><c>C1</c></a></r:nuko>
<r:root xmlns:r="http://foo.bar"><a><b>B1</b><c>C1</c></a></r:root>
(3 rows)

xmldb=#

で、これに対して以下のようにSELECTしてみる。
xmldb=# select data from xml_test WHERE (xpath('/*[local-name() = "nuko" and namespace-uri() ="http://hoge.hoge"]', data )::text[])[1] <> '';
data

                                                                                                                                            • -

<r:nuko xmlns:r="http://hoge.hoge"><a><b>B1</b><c>C1</c></a></r:nuko>
(1 row)

xmldb=# select data from xml_test WHERE (xpath('/*[local-name() = "root" and namespace-uri() ="http://hoge.hoge"]', data )::text[])[1] <> '';
data

                                                                                                                                            • -

<r:root xmlns:r="http://hoge.hoge"><a><b>B1</b><c>C1</c></a></r:root>
(1 row)

xmldb=# select data from xml_test WHERE (xpath('/*[local-name() = "root" and namespace-uri() ="http://foo.bar"]', data )::text[])[1] <> '';
data

                                                                                                                                        • -

<r:root xmlns:r="http://foo.bar"><a><b>B1</b><c>C1</c></a></r:root>
(1 row)

xmldb=#

一応、所要の動きになってくれた。DELETE/UPDATEも同じようにできるはず。

追記

  • しかしこれだと文書数が多くなったときに関数インデックスが使えないという問題が・・・。
  • まあ、関数インデックスを使わなくてもルート要素名と名前空間URIxml格納カラムとは別に持てばいいのだろうけど、なんかそれは負けた気がする。
  • ルート要素名を返す関数と名前空間URIを返すC関数を作ってCREATE FUNCTIONで組み込むしかないのかなあ。それはそれで面倒な話。