7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PostgreSQL の JSONB でよく使われる操作をまとめてみた

Last updated at Posted at 2022-05-25

社内で PostgreSQL の JSONB 型が使われることが多いです。

JSONB 型は便利ですがハマりやすくもあると感じました。よく使う操作をまとめてみます。

動作確認した環境

SELECT version();
PostgreSQL 12.8 (Debian 12.8-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit

基本操作

  • ->: 指定したキーの値を JSONB として取得する
  • ->>: 指定したキーの値を TEXT として取得する
  • #>: パスで指定したキーの値を JSONB として取得する
  • #>>: パスで指定したキーの値を TEXT として取得する
SELECT ('{"key1": "hoge", "key2": ["a", "b"]}'::JSONB)->'key1'; -- "hoge"
SELECT ('{"key1": "hoge", "key2": ["a", "b"]}'::JSONB)->'key2'->0; -- "a"

SELECT ('{"key1": "hoge", "key2": ["a", "b"]}'::JSONB)->>'key1'; -- hoge
SELECT ('{"key1": "hoge", "key2": ["a", "b"]}'::JSONB)->'key2'->>0; -- a

SELECT ('{"key1": "hoge", "key2": ["a", "b"]}'::JSONB)#>'{key1}'; -- "hoge"
SELECT ('{"key1": "hoge", "key2": ["a", "b"]}'::JSONB)#>'{key2,0}'; -- "a"

SELECT ('{"key1": "hoge", "key2": ["a", "b"]}'::JSONB)#>>'{key1}'; -- hoge
SELECT ('{"key1": "hoge", "key2": ["a", "b"]}'::JSONB)#>>'{key2,0}'; -- a

JSON にキーを追加する、書き換える

jsonb_set で指定したキーの値を書き換えた JSONB を取得できます。

-- キーの追加
SELECT
  jsonb_set(
    '{"key1": "hoge", "key2": ["a", "b"]}'::JSONB
    ,'{key3}'
    ,'1'
  )
;
{"key1": "hoge", "key2": ["a", "b"], "key3": 1}
-- キーの値の変更
SELECT
  jsonb_set(
    '{"key1": "hoge", "key2": ["a", "b"]}'::JSONB
    ,'{key2}'
    ,'1'
  )
;
{"key1": "hoge", "key2": 1}

JSONB 配列を作成する

  • テストデータの準備:
DROP TABLE IF EXISTS t_sample;
CREATE TABLE t_sample AS
SELECT
  gen_random_uuid() AS uuid
  ,generate_series(1, 10) AS value
;

SELECT * FROM t_sample;
uuid value
faa0c7a3-8c4e-4d06-ad2f-e1ccb8ef2425 1
f30851d4-60e3-444d-b540-be2dc61d2ba9 2
79f1e731-a93b-4139-aa42-7d2006aab4dd 3
bc930450-0eef-4b10-a285-d2d0bc065f7f 4
d79ea66b-8872-485c-98df-151cc5ddc84e 5
802afc33-ff4b-4e66-9f59-176e4331e364 6
6f23b3e2-318d-49e1-a518-cee0ec2c0d9e 7
3069ea78-02b0-4e1c-b3a7-6ee41e9e2ee6 8
aa2ce439-01ad-4589-b1ba-80f32685fe8b 9
05b23675-89e6-48f3-bfba-acc24ca1b40a 10

CREATE TABLE ... AS とすると SELECT した内容でテーブルを作成でき、動作確認に便利です。

jsonb_build_object + jsonb_agg

JSONB 配列を作成したい場合、jsonb_build_objectjsonb_agg を使うことでやりたいことができそうです。

-- jsonb_agg と jsonb_build_object でレコードをまとめる
SELECT
  jsonb_agg(
    jsonb_build_object(
      'uuid', t1.uuid
      ,'value', t1.value
    )
  )
FROM
  t_sample AS t1
;
jsonb_agg
[{"uuid": "faa0c7a3-8c4e-4d06-ad2f-e1ccb8ef2425", "value": 1}, {"uuid": "f30851d4-60e3-444d-b540-be2dc61d2ba9", "value": 2}, {"uuid": "79f1e731-a93b-4139-aa42-7d2006aab4dd", "value": 3}, {"uuid": "bc930450-0eef-4b10-a285-d2d0bc065f7f", "value": 4}, {"uuid": "d79ea66b-8872-485c-98df-151cc5ddc84e", "value": 5}, {"uuid": "802afc33-ff4b-4e66-9f59-176e4331e364", "value": 6}, {"uuid": "6f23b3e2-318d-49e1-a518-cee0ec2c0d9e", "value": 7}, {"uuid": "3069ea78-02b0-4e1c-b3a7-6ee41e9e2ee6", "value": 8}, {"uuid": "aa2ce439-01ad-4589-b1ba-80f32685fe8b", "value": 9}, {"uuid": "05b23675-89e6-48f3-bfba-acc24ca1b40a", "value": 10}]

jsonb_agg と 一緒に FILTERORDER BY が利用できて便利です。

-- value が偶数のレコードだけ value の降順にして JSONB 配列にする
SELECT
  jsonb_agg(
    jsonb_build_object(
      'uuid', t1.uuid
      ,'value', t1.value
    )
    ORDER BY t1.value DESC
  ) FILTER (WHERE t1.value % 2 = 0)
FROM
  t_sample AS t1
;
jsonb_agg
[{"uuid": "05b23675-89e6-48f3-bfba-acc24ca1b40a", "value": 10}, {"uuid": "3069ea78-02b0-4e1c-b3a7-6ee41e9e2ee6", "value": 8}, {"uuid": "802afc33-ff4b-4e66-9f59-176e4331e364", "value": 6}, {"uuid": "bc930450-0eef-4b10-a285-d2d0bc065f7f", "value": 4}, {"uuid": "f30851d4-60e3-444d-b540-be2dc61d2ba9", "value": 2}]

JSONB 配列を展開する

ストアドファクションに引数で JSONB 配列を渡して、データを一括で INSERT する、みたいなことをよくおこないます。

JSONB 配列の展開に便利な関数は以下です。

jsonb_array_elements

jsonb_array_elements を利用すると、JSONB 配列の各要素を順番に JSONB として取り出すことができます。

SELECT
  t1->>'key1' AS key1
  ,(t1->>'key2')::NUMERIC AS key2
FROM
  jsonb_array_elements('
    [
      {"key1": "foo", "key2": 20},
      {"key1": "bar", "key2": 2}
    ]
  ') AS t1
;
key1 key2
foo 20
bar 2

t1->>'key1' でアクセスしているように、t1 が JSONB になってしまうことが(個人的に)少しわかりにくく感じました。

jsonb_to_recordset

一方で、jsonb_to_recordset を利用すると、JSONB 配列をそのままテーブルのように扱って各要素を取り出すことができます。

以下では文字列を TEXT 型、数値を NUMERIC として取り出しています。

SELECT
  t1.key1
  ,t1.key2
FROM
  jsonb_to_recordset('
    [
      {"key1": "foo", "key2": 20},
      {"key1": "bar", "key2": 2}
    ]
  ') AS t1(
    key1 TEXT
    ,key2 NUMERIC
  )
;
key1 key2
foo 20
bar 2

AS 以降でキー名と型を指定する必要があります。
FROM 句にテーブルを指定したのと同じような感じでSELECT 句を書くことができます。
毎回 ->>'key' のような指定をしなくて済むので、jsonb_array_elements よりも積極的に使いたいと個人的に感じる方法です。

JSONB 配列 ←→ 配列

  • JSONB 配列 → 配列にする:
SELECT ARRAY(SELECT jsonb_array_elements_text('["a", "b", "c"]'::JSONB));
  • 配列を JSONB 配列にする
SELECT to_jsonb(ARRAY['a', 'b', 'c']);

ハマったこと

JSONB 型を使ってハマったことを書いてみました。

-> と ->> の挙動の違い

値があるかどうか判定するときに注意が必要です。

-- -> を使った場合
SELECT '{"key1": "value1", "key2": null}'::JSONB->'key2' IS NULL; -- FALSE(注意)
SELECT '{"key1": "value1", "key2": null}'::JSONB->'key3' IS NULL; -- TRUE
SELECT '{"key1": "value1", "key2": null}'::JSONB->>'key2' IS NULL; -- TRUE
SELECT '{"key1": "value1", "key2": null}'::JSONB->>'key3' IS NULL; -- TRUE

null のときとキーがないときを同じものとして判定したいときは、->> を利用するのがよさそうです。

to_jsonb(*) ができない

SELECT to_jsonb(*) FROM t_sample; -- エラーになる
ERROR:  function to_jsonb() does not exist
LINE 1: SELECT to_jsonb(*) FROM t_sample;
               ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
SELECT to_jsonb(t1.*) FROM t_sample AS t1; -- うまくいく
to_jsonb
{"uuid": "af90648c-8508-42c9-bd12-5c85234b0771", "value": 1}
{"uuid": "6515b3b5-70d0-437c-a6b4-cea8e1eb66f4", "value": 2}
{"uuid": "a02bdf0a-a15f-4a88-ac02-b35cbbca51bc", "value": 3}
{"uuid": "8778a595-26a4-4f31-9070-98c6014b1438", "value": 4}
{"uuid": "b94ce2bc-efaa-4b0d-a207-2078083a1500", "value": 5}
{"uuid": "f146f2a2-68b3-4de4-b646-0d73955cdebc", "value": 6}
{"uuid": "8ecb62da-4e63-406d-a96c-91d608dda1da", "value": 7}
{"uuid": "6d55e4a9-6db7-4b53-ad31-874ac05dbcb0", "value": 8}
{"uuid": "ca0a0598-637d-415b-959f-404f988d7aba", "value": 9}
{"uuid": "19e973c4-0b62-4bde-9de9-9e0fa7b6169b", "value": 10}
7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?