PostgreSQLでXML(1): まずは使ってみる

はじめに

はてさて、諸事情で最近PostgreSQL8.4のXMLサポートを使ってみたりしているのですが、 これが結構面白い! てなわけで、ちょっとずつメモを残してみることにしました。 なお、ここではPostgreSQL依存上等! というノリでやってますので、 別にポータビリティは全く気にしてません。 それを前提にしたメモということでご承知おきを。

FreeBSD 8.1-RELEASEで環境を作っているのですが、 普通にでports/databases/postgresql84-serverを突っ込めばXMLサポートは付いてきます。 SQLのテーブルの中にXMLという型が使えるようになっていて、 それに対してXPathでWHEREの条件を書いたり、SELECTの対象にしたりすることができます。

XML型のINSERT

例えば超単純な次のようなテーブルを考えます。

CREATE TABLE samplexml (
    id SERIAL PRIMARY KEY,
    xmldoc XML
);

ここのXML部分には、次のように適当なXMLを突っ込むことができるわけです。

INSERT INTO samplexml (xmldoc)
VALUES ('<document><h1>挨拶</h1><p id="hello">こんにちは</p><p>さようなら</p></document>');

XPathを用いたSELECT

とりあえず条件を入れずにSELECTしてみましょう。 <h1>というエレメントを抜いてくるには次のようにします。

pgsql=# SELECT xpath(E'//h1/text()', xmldoc) FROM samplexml;
 xpath  
--------
 {挨拶}
(1 行)

結果が "挨拶" ではなく、"{挨拶}"になっているところに注目です。 これは、一つのXPathの検索によって、 XML文書の複数箇所が検索されてくる可能性があるので、 文字列ではなく、文字列の配列として値を返してきているのです。 たとえば<h1>ではなく<p>を検索すると次のように返ってきます。

pgsql=# SELECT xpath(E'//p/text()', xmldoc) FROM samplexml;
          xpath          
-------------------------
 {こんにちは,さようなら}
(1 行)

まあ、どうしても最初の1個だけとか限定を付けて文字列(PostgreSQLのtext型)として返したいのであれば、次のようにtext型の配列にキャストした上でインデックス番号を付加すればOKです。

pgsql=# SELECT (xpath(E'//p/text()', xmldoc)::text[])[1] FROM samplexml;
   xpath    
------------
 こんにちは
(1 行)

あるいは、array_to_string()を使って空白区切りで1つの文字列として返すようなこともできます。

pgsql=# SELECT array_to_string(xpath(E'//p/text()', xmldoc)::text[], ' ') FROM samplexml;
    array_to_string    
-----------------------
 こんにちは さようなら
(1 行)

2つの形式の条件検索

PostgreSQLでXMLを条件検索する場合は、2つのレベルがあります。 一つはWHEREによる検索で、これは通常のSQLと同様、 XPathの値によって、SQL的な「行」を検索します。 もうひとつはXPathの条件による検索で、 これはSELECTの中で、XML中のどの部分を表示するかについて検索を行うわけです。

たとえば、上記XML中での双方の例を示します。

前者の例

(複数行のデータが入っていることを仮定して) id="hello" という属性が設定されたエレメントが含まれるXML文書を検索し、その文書中の<h1>エレメントの内容を表示する。

pgsql=# SELECT xpath(E'//h1/text()', xmldoc) FROM samplexml 
pgsql-# WHERE (xpath(E'//@id', xmldoc)::text[]) @> '{hello}';
 xpath  
--------
 {挨拶}
(1 行)
後者の例

XML文書中でid="hello"属性が設定されたというエレメントの内容を表示する

pgsql=# SELECT xpath(E'//*[@id="hello"]/text()', xmldoc) FROM samplexml;
    xpath     
--------------
 {こんにちは}
(1 行)

もちろん、この双方を組み合わせて利用することも可能です。

前者の例中に"@>"という演算子が含まれていますが、 これは配列を集合と捉え、左辺の集合が右辺を包含しているかどうかを示す演算子です。 同様の検索は、any()を使って次のように実行することもできるのですが、 性能上の理由でおすすめできません。この理由は後日。

pgsql=# SELECT xpath(E'//h1/text()', xmldoc) FROM samplexml
pgsql-# WHERE 'hello' = any(xpath(E'//@id', xmldoc)::text[]);
 xpath  
--------
 {挨拶}
(1 行)

(追記) この「おすすめしない」理由は第2回に書きました。

ということで、また後日に続きます。

関連記事