PostgreSQLで扱える半構造化データ型3種

PostgreSQL Advent Calendar 2013 の7日目の記事です。

前回 は配列や複合型などの既存の型を組み合わせることができる型を紹介したので、今回はpostgresで非構造化データ型を3種類紹介します。

半構造化データ型

厳密に定義はないと思うのですが、本エントリでは厳密な定義を持たずに、内部に配列やハッシュなどの構造と、値を持つようなデータを指すこととします。

postgresではいくつか半構造化データのフォーマットを型として扱うことができます。

使いどころ

RDBMSでは、あらかじめテーブル設計を行って、各テーブルにどの様な値が入るかを型や制約を用いてスキーマとして定義します。一般的にデータの寿命は長く、一旦データベースに意図しない値が入ってしまうと、後々に渡って非常に厄介な事態を引き起こします。そのため、そうした意図しないデータを、永続化される前に水際で食い止めることはDBMSの重要な役割の一つです。

その一方で、アプリケーションの開発/運用を進めていく中で、入れたいデータの内容が変わり、スキーマの変更が必要になることがあります。RDBMSではスキーマの変更は大きな負荷となったり大きなロックが必要となったりするため、中々気軽にデータを変更することができません。

予め入れたいデータの内容が変わることが予想される場合に、非構造化データ型を用いることで、スキーマの変更なく柔軟にデータの変更に対応することが可能になります。

あるいは、単純に外部で利用されているフォーマットのデータをそのままDBの中に入れてしまいたいというケースも多いかもしれません。

いずれにしても、半構造化データ型を用いてしまうと、冒頭で述べたスキーマでのデータのチェックが行いにくくなるため、基本的には通常のテーブルとして定義しつつ、柔軟性が必要になる限られた箇所にのみ半構造化データ型を用いる方がいいでしょう。

1. XML型

postgresでは組み込み型としてXMLを利用することができます。

XML型

SELECT '<foo><bar>hoge</bar><bar>fuga</bar></foo>'::xml

XMLを扱う組み込みの関数が多く用意されています。

特に、標準化されたXMLを操作する構文のXPathを扱う関数があるため、XML内の任意の値を取り出すことが簡単にできます。XPathが複数の値を返すように場合には配列を返してくれます。

SELECT xpath('//foo/bar/text()/', <foo><bar>hoge</bar><bar>fuga</bar></foo>'::xml);

xpath
-------------
{hoge,fuga}
(1 row)

インデックスを張りたい場合も、上記のxpath関数に、関数の結果にインデックスを張れる関数インデックス、配列などのインデックスに用いるginインデックスを組み合わせることで、XMLないの任意の箇所にインデックスを張ることが可能です。

2. hstore型

hstore型はkey-valueのペアを扱う型です。組み込みではないのですが、コアと一緒に配布されるcontribと呼ばれる拡張のセットに含まれているため、通常のインストールを行っていれば簡単に利用することができます。

hstore

$ sudo yum install postgresql-contrib # 公式のリポジトリを利用している場合
$ psql your_database -c 'CREATE EXTENSION hstore'

もちろん任意のkeyのvalueを取り出すことができます。他にも多くの演算子/関数が用意されています。

-- hstore型の文字列表現
SELECT 'a => 1, b => 2'::hstore;

-- 値の取り出し
SELECT ('a => 1, b => 2'::hstore)->'a';

 ?column?
----------
 1
(1 row)

インデックスはXML型と同じくginインデックスを用います。

hstoreは比較的古くからあるモジュールなのですが、最近でも活発に開発が進められているため、今後さらにパフォーマンスの向上などが望めると思います。

3. JSON型

9.2からJSON型が追加されました。9.2では主にバリデーションのみだったのですが、現在の最新バージョンである9.3で大幅にAPIが追加され、SQLからJSON内部の値を取り出せるようになりました。

JSONデータ型

JSON関数と演算子

-- JSON型の文字列表現
SELECT '{"a":1, "b":[1,2,3]}'::json;

-- JSON内の値の取り出し
SELECT ('{"a":1, "b":[1,2,3]}'::json)->'b'->1;

?column?
----------
2
(1 row)

Comments