PostgreSQLには、JSONデータを管理する型としてjson型とjsonb型があります。
これらは登録は簡単なのですが、検索して利用する際は少し癖があるので、解説しようとお思います。
PostgreSQL version: v12.6
json型とjsonb型の違い
ドキュメント から抜粋しました。
キー順序や重複を利用するような特殊ケースを除き、jsonb型を利用する方が良さそうです。
json型 | jsonb型 |
---|---|
入力テキストの正確なコピーを格納 (空白、キー順序、重複キーなども保持) |
入力テキストをパースした結果をバイナリ形式で格納 (空白、キー順序は保持しない。重複キー重複キーは最後の値だけを保持) |
利用時に再解析する必要があるため、利用時に時間がかかる | 格納時にオーバヘッドがあるが、利用時は高速に利用できる |
インデックスをサポートしていない | インデックスをサポートしている |
データ登録
people
テーブルにdetail
というjsonb型の項目を登録するサンプルになります。
こちらのデータを元に後続のSQLの検証を行います。
create table people (
detail jsonb
);
insert into people(detail)
values ('{"name": "太郎", "age": 35, "children": [{"name": "二郎", "age": 9}, {"name": "三郎", "age": 6}]}')
;
JSON演算子でJSONを扱う
ドキュメントを元に解説したいと思います。
演算子は、->
・->>
・#>
・#>>
の四つがあります。
記号の意味
JSON演算子は、-
・#
・>
・>>
の記号の組合せで表現されます。
記号に意味があるので、覚えておくと良いと思います。
-
-
: 子要素を取得する -
#
: パスを指定して要素を取得する -
>
: JSONオブジェクトとして要素を取得する -
>>
: テキストとして要素を取得する
また、子要素を取得する際は、以下の二つの指定方法があります
- JSONオブジェクトの場合
- keyを指定する
-
'{"hoge": "fuga"}'::json->>'hoge'
でfuga
を取得できる
- JSON配列の場合
- 添字を指定する
-
'["hoge", "fuga"]'::json->>0
でhoge
を取得できる
具体例
people.detail
の内容に対して検索をかける形で記載していきますが、以下の二つのSQLは同じ結果になります。
select detail->'name' from people;
select '{"name": "太郎", "age": 35, "children": [{"name": "二郎", "age": 9}, {"name": "三郎", "age": 6}]}'::jsonb->'name';
子要素を取得(->>
,->
)
-
->>
: 子要素をテキストとして取得するselect detail->>'name' from people; --> "太郎"(text型) select detail->>'age' from people; --> "35"(text型) select detail->>'children' from people; --> "[{"age": 9, "name": "二郎"}, {"age": 6, "name": "三郎"}]"(text型)
-
->
: 子要素をjsonオブジェクトとして取得するselect detail->'name' from people; --> 太郎(jsonb型) select detail->'age' from people; --> 35(jsonb型) select detail->'children' from people; --> [{"age": 9, "name": "二郎"}, {"age": 6, "name": "三郎"}](jsonb型)
-
->
はjson/jsonb型を返すので、演算子を重ねて孫・曽孫・・・要素を取得することができるselect detail->'children'->0 from people; --> {"age": 9, "name": "二郎"}(jsonb型) select detail->'children'->0->'name' from people; --> 二郎(jsonb型) select detail->'children'->>0 from people; --> "{"age": 9, "name": "二郎"}"(text型)
パス指定で子や孫要素を直接取得する(#>>
,#>
)
->
を使って、順番に要素を辿っていくことはできますが、パスを指定して一気に取得することもできます。
-
#>>
: パス指定した要素をテキストとして取得するselect detail#>>'{children,0}' from people; --> {"age": 9, "name": "二郎"}(text型) select detail#>>'{children,0,name}' from people; --> 二郎(text型)
-
#>
: パス指定した要素をJSONオブジェクトとして取得するselect detail#>'{children,0}' from people; --> {"age": 9, "name": "二郎"}(jsonb型) select detail#>'{children,0,name}' from people; --> 二郎(jsonb型)
JSON関数
JSON作成関数とJSON処理関数があります。
JSON作成関数は見ればわかる内容なので、特にここでは解説しません。
json型を扱う場合はjson_xxx
、jsonb型を扱う場合はjsonb_xxx
という関数名になりますが、使い方はどちらも同じです。
ここではjson型を扱う関数をいくつか紹介します。
json_array_length: JSON配列の長さを返す
select json_array_length('["aaa", "bbb", "ccc"]'::json);
--
3
json_each: JSONオブジェクトをkey/value組合せの集合を返す
JSONオブジェクトをkey/value組合せで別レコードに分けて、record型で返します。
select json_each('{"age": 9, "name": "二郎", "metas": ["hoge", "fuga"]}'::json);
--
(age,9)
(name,"二郎")
(metas,["hoge", "fuga"])
record型のままだと扱いにくいので、自分はjson型に変換して、扱うようにしてます。
with records as (
select json_each('{"age": 9, "name": "二郎", "metas": ["hoge", "fuga"]}'::json) as record
)
select
row_to_json(record)->'key' as key
, row_to_json(record)->'value' as value
from
records
;
--
"age",9
"name","二郎"
"metas",["hoge", "fuga"]
json_object_keys: JSONオブジェクトのキーの集合を返す
select json_object_keys('{"age": 9, "name": "二郎", "metas": ["hoge", "fuga"]}'::json);
--
age
name
metas
JSONオブジェクトの値の集合を返す
json_object_values
関数は存在しませんので、同等のことがやりたい場合は、
json_each
でrecord型の集合に変換して、row_to_json
でjson型に戻して、->'value'
で値だけ取得するようなSQLを組む必要があります。
select row_to_json(json_each('{"age": 9, "name": "二郎", "metas": ["hoge", "fuga"]}'::json))->'value';
--
9
"二郎"
["hoge", "fuga"]
json_array_elements: JSON配列をレコード分割して返す
select json_array_elements('["aaa", "bbb", "ccc"]'::json);
--
"aaa"
"bbb"
"ccc"
JSON配列をarray型に変関する
JSON配列をarray型に直接変換する関数は存在しません(たぶん)。
そのため、一旦json_array_elements
やjson_array_elements_text
でレコードに分けて、
それをarray_agg
で改めてarray型に集約する必要があります。
with data as (
select json_array_elements_text('["aaa", "bbb", "ccc"]'::json) as record
)
select
array_agg(record)
from
data
;
--
{aaa,bbb,ccc}
array型になっていれば、array関数を自由に使えるので、色々加工できます。
with data as (
select json_array_elements_text('["aaa", "bbb", "ccc"]'::json) as record
)
select
concat('【', array_to_string(array_agg(record), '】【'), '】')
from
data
;
--
【aaa】【bbb】【ccc】