皆さん、PostgreSQL使ってますか?
オープンソースRDBであれば、ほぼこれ一択。というプロダクトですよね。
と、まずはじめに MySQL 派に喧嘩を売ってみました。ちなみに当方は喧嘩を売られても買いません。
さて、 PostgreSQL にはJSONデータを格納するための json
型と jsonb
型の2つがサポートされています。
一般的にはバイナリデータで保存される jsonb
型のほうがパフォーマンスが高いので、通常は jsonb
型を使うと思うのですが、 jsonb
型にはいくつかの落とし穴があります。今回はそれをお伝えします。
jsonbの落とし穴
- 同じキーが集約される。
- キーが自動的にソートされてしまう。
になります。順番に見てみましょう。
同じキーが集約される。
JSONの事実上の仕様である RFC 8259 によると
When the names within an object are not unique,
the behavior of software that receives such an object is unpredictable.
Many implementations report the last name/value pair only.
Other implementations report an error or fail to parse the object,
and some implementations report all of the name/value pairs, including duplicates.
(上記の参考訳)
オブジェクト内の名前が一意でなければ、そのようなオブジェクトを受け取るソフトウェアの動作は予測できません。
多くの実装は、最後の名前/値のペアのみを報告します。
その他の実装は、エラーを報告するかオブジェクトの解析に失敗しますが、重複を含むすべての名前/値のペアを報告する実装もあります。
となっており、キーの重複については許容されている。と読めます。
しかしながら、 jsonb
型にストアする場合は、重複キーは集約されます。ちなみにどの値が残されるのかは不定です。現在の実装(PostgreSQL 12)だと後勝ちのようですね。
insert into json_tables (data) values ('{"key": 1, "key": 2, "key": 3}');
select * from json_tables;
data
------------
{"key": 3}
(1 row)
これ自体はそういう実装でもまぁそうだよね。で済むんですが、次が厄介です。
キーが自動的にソートされてしまう
なんと jsonb
型はストア時にキーがソートがされてしまいます。
insert into json_tables (data) values ('{ "c": "c", "a": "a", "b": "b"}');
select * from json_tables;
data
--------------------------------
{"a": "a", "b": "b", "c": "c"}
(1 rows)
これは jsonb
型ではハッシュの 順番が保持できない。 ということを意味しています。
本来ハッシュの順番は保証されないので冷静に考えれば当然ですが、見た目上の順番があるのでそれが保持されると思いがちです。
表示の順番がなんかおかしい。というバグの原因を探していたら実はこれだった。ということがありました。
特に配列とハッシュの区別が無いPHPプログラマーはその罠に陥りがちかも。
rubyのハッシュも1.9から順番が保証されるようになったので同じ罠に陥るかもしれません。
順番を保持したい場合は配列を使う必要があります。
ちなみに json
型であればこのような問題はありません。これは、入力されたデータをそのままテキストデータとして保持しているからです。
その代わり容量の効率や検索時のパフォーマンスが犠牲になります。
insert into json_tables (data) values ('{"key": 1, "key": 2, "key": 3}');
select * from json_tables ;
data
--------------------------------
{"key": 1, "key": 2, "key": 3}
(1 row)
insert into json_tables (data) values ('{"a": "a", "c": "c", "b": "b"}');
select * from json_tables ;
data
--------------------------------
{"a": "a", "c": "c", "b": "b"}
(1 rows)
こうやって json
型にするか jsonb
型にするかで悩めるのも PostgreSQl が MySQL に優れているところですね。
それでは良い PostgreSQL ライフを。