web-dev-qa-db-ja.com

複雑なjsonb列を更新する方法は?

次の定義の表があります。

_create table json_test (
    filter_data jsonb);
_

そして私はそれにこのような値を挿入します:

_'{"task_packets": [
    {
        "state": "PROCEEDING",
        "task_id": 1001
    },
    {
        "state": "REVERTING",
        "task_id": 1002
    }
]}'
_

このjsonb列を次のように更新します。

_'{"task_packets": [
    {
        "state": "DONE",
        "task_id": 1001
    },
    {
        "state": "REVERTING",
        "task_id": 1002
    }
]}'
_

つまり、task_packetsの配列内の指定された_task_id_で値の状態を変更したいです。 jsonb_set()関数を_#-_演算子と組み合わせて使用​​することをお勧めします(最初に配列から値を削除してから、更新された状態でそれに追加します)。
どうすればできますか?

1
ArtemP

私がコメントしたように、これはこのようなテーブルで正規化されたDBレイアウトでより効率的です

CREATE TABLE task_packets (
  task_id int PRIMARY KEY
, state text NOT NULL
-- or: state_id int NOT NULL REFERENCES state(state_id) ...
);

特に、一意のtask_id番号を強制するPK制約を設定できます。そして、あなたが望むUPDATEは簡単です。


しかし質問に答える

宛先SELECT

SELECT *
FROM   json_test jt
     , LATERAL (
   SELECT jsonb_set(filter_data
                  , '{task_packets}'
                  , jsonb_agg(CASE WHEN elem->>'task_id' = '1001'
                               THEN jsonb_set(elem, '{state}', to_jsonb(text 'DONE'))
                               ELSE elem
                             END)) AS filter_data_new
   FROM   jsonb_array_elements(filter_data->'task_packets') elem
   ) tp
WHERE  jt.filter_data @> '{"task_packets": [{"task_id": 1001}]}';

私はLATERAL joinを提案します。特に、プレーンで誤ってひとまとめにされる可能性がある複数の一致する行の可能性を除外します参加する。

宛先UPDATE

UPDATE json_test
SET    filter_data =
   (
   SELECT jsonb_set(filter_data
                  , '{task_packets}'
                  , jsonb_agg(CASE WHEN elem->>'task_id' = '1001'
                                THEN jsonb_set(elem, '{state}', to_jsonb(text 'DONE'))
                                ELSE elem
                              END))
   FROM   jsonb_array_elements(filter_data->'task_packets') elem
   )
WHERE  filter_data @> '{"task_packets": [{"task_id": 1001}]}';

同じことは、UPDATE(またはSELECTにも)のcorrelated subqueryを使用して実装できます。

大きなテーブルでこれを高速にするには、適切なindex、理想的にはjsonb_path_opsインデックスが必要です。

7

これを試して

update 
    json_test 
set
    filter_data  = 
        jsonb_set(
            filter_data ,
            array['task_packets', elem_index::text, 'state'],
            '"DONE"'::jsonb,
            true)
from (
    select 
     pos- 1 as elem_index
    id 
    from 
        json_test, 
        jsonb_array_elements(filter_data ) with ordinality arr(elem, pos)
    where
       elem->>'task_id' = '1001'
    ) sub
where sub.id=id 
0
Bharat Bhamare

これは非常に役立ちましたが、配列内の複数のキーを一度に更新する必要があり、9.5以上の連結ショートカットを使用しました ||

update public.contactsdb
set data = (select jsonb_set(data, '{Contacts}', jsonb_agg(
    elem || '{"Summary": "A User", "Email":"[email protected]"}'))
    from jsonb_array_elements(data->'Contacts') elem )
  where id in (select id from contactsdb,
   jsonb_array_elements(data->'Contacts') as elems where elems->'Summary' is not null)

ブロック内の書式設定で申し訳ありません。正しく理解できません。 modはそれを持っています

0
cyclops