はじめに
PostgreSQLでJSONBデータを扱う時の、よく使う方法のメモ。
基本的には以下のマニュアルの通りだが、備忘録として。
[9.15. JSON関数と演算子]
(https://www.postgresql.jp/document/9.4/html/functions-json.html)
サンプルデータ1(単一データ)
まずは、単一のユーザ情報をJSONB形式で保持するサンプルテーブルを用意。
CREATE TABLE user_info (id serial, info jsonb);
INSERT INTO user_info (id, info) VALUES (1, '{"name": "user1-1", "age": 20}');
INSERT INTO user_info (id, info) VALUES (2, '{"name": "user1-2", "age": 21}');
INSERT INTO user_info (id, info) VALUES (3, '{"name": "user2-1", "age": 22}');
INSERT INTO user_info (id, info) VALUES (4, '{"name": "user2-2", "age": 23}');
postgres=# SELECT * FROM user_info;
id | info
----+--------------------------------
1 | {"age": 20, "name": "user1-1"}
2 | {"age": 21, "name": "user1-2"}
3 | {"age": 22, "name": "user2-1"}
4 | {"age": 23, "name": "user2-2"}
(4 行)
フィールドの抽出
特定のフィールドだけを抽出したいときは、->> 演算子を使用する。
postgres=# SELECT *, info->>'name' user_name, info->>'age' user_age
postgres-# FROM user_info
postgres-# WHERE (info->>'age')::int > 20;
id | info | user_name | user_age
----+--------------------------------+-----------+----------
2 | {"age": 21, "name": "user1-2"} | user1-2 | 21
3 | {"age": 22, "name": "user2-1"} | user2-1 | 22
4 | {"age": 23, "name": "user2-2"} | user2-2 | 23
(3 行)
※ 「info->>'name'」のように、フィールド名にシングルクォーテーションが必要。
※ JSONデータは型を持っていないため、取り出した後でWHERE句の判定などで使用する場合は、「(info->>'age')::int」のように型指定する必要がある。
※ カラム名を指定しないと「?column?」になるようなので、別名を付けたほうがよい。
サンプルデータ2(複数データ)
次に、複数のユーザ情報のIDを配列で保持する、「ユーザグループ」を用意。
postgres=# CREATE TABLE user_group (id serial, name varchar(50), users jsonb);
postgres=# INSERT INTO user_group (id, name, users ) VALUES (1, 'Group1', '[1, 2]');
postgres=# INSERT INTO user_group (id, name, users ) VALUES (2, 'Group2', '[3, 4]');
postgres=# SELECT * FROM user_group;
id | name | users
----+--------+--------
1 | Group1 | [1, 2]
2 | Group2 | [3, 4]
(2 行)
展開
JSONBデータを行データに展開したい場合は、jsonb_array_elements と CROSS JOIN を使用する。
以下のように、ユーザID(1,2,3,4)を縦並びにできる。
postgres=# SELECT user_group.*, users.value user_id FROM user_group
CROSS JOIN LATERAL jsonb_array_elements (user_group.users) users;
id | name | users | user_id
----+--------+--------+---------
1 | Group1 | [1, 2] | 1
1 | Group1 | [1, 2] | 2
2 | Group2 | [3, 4] | 3
2 | Group2 | [3, 4] | 4
(4 行)
※ フィールド名のないJSONデータはデフォルトで「value」となるようなので、注意。
上記では、「users.value user_id」のように別名を付けている。
結合
ユーザ情報とユーザグループを結合したい場合は、上記で展開した「ユーザグループ」の行データと「ユーザ情報」を JOIN で結合する。
postgres=# WITH users AS (
postgres(# SELECT user_group.*, users.value user_id FROM user_group
postgres(# CROSS JOIN LATERAL jsonb_array_elements (user_group.users) users
postgres(# )
postgres-# SELECT users.id group_id, users.name, users.users
postgres-# , user_info.id user_id, user_info.info
postgres-# FROM users
postgres-# INNER JOIN user_info ON (users.user_id)::int = user_info.id
postgres-# ORDER BY user_info.id;
group_id | name | users | user_id | info
----------+--------+--------+---------+--------------------------------
1 | Group1 | [1, 2] | 1 | {"age": 20, "name": "user1-1"}
1 | Group1 | [1, 2] | 2 | {"age": 21, "name": "user1-2"}
2 | Group2 | [3, 4] | 3 | {"age": 22, "name": "user2-1"}
2 | Group2 | [3, 4] | 4 | {"age": 23, "name": "user2-2"}
(4 行)
集約
ユーザ情報とユーザグループを結合した上で、元の「ユーザグループ」と同じ構成に戻したい場合は、GROUP BY で集約する。
行データをJSON形式に配列に集約したい場合は、array_to_json、array_agg を使用する。
postgres=# WITH users AS (
postgres(# SELECT user_group.*, users.value user_id FROM user_group
postgres(# CROSS JOIN LATERAL jsonb_array_elements (user_group.users) users
postgres(# )
postgres-# , user_info_group AS (
postgres(# SELECT users.id group_id, users.name, users.users
postgres(# , user_info.id user_id, user_info.info
postgres(# FROM users
postgres(# INNER JOIN user_info ON (users.user_id)::int = user_info.id
postgres(# ORDER BY user_info.id
postgres(# )
postgres-# SELECT group_id, name, array_to_json(array_agg(user_info_group.info)) info
postgres-# FROM user_info_group
postgres-# GROUP BY group_id, name
postgres-# ORDER BY group_id;
group_id | name | info
----------+--------+-----------------------------------------------------------------
1 | Group1 | [{"age": 20, "name": "user1-1"},{"age": 21, "name": "user1-2"}]
2 | Group2 | [{"age": 22, "name": "user2-1"},{"age": 23, "name": "user2-2"}]
(2 行)
※ JSONデータの中もソートしたい場合は、集約する前にソートする。
上記の場合、user_info_group のテーブルを生成する時に「ORDER BY user_info.id」としている。