如何更新复杂的 jsonb 列?

Art*_*emP 1 postgresql update json postgresql-9.5

我有一个具有以下定义的表:

create table json_test (
    filter_data jsonb);
Run Code Online (Sandbox Code Playgroud)

我插入这样的值:

'{"task_packets": [
    {
        "state": "PROCEEDING",
        "task_id": 1001
    },
    {
        "state": "REVERTING",
        "task_id": 1002
    }
]}'
Run Code Online (Sandbox Code Playgroud)

现在我想将此jsonb列更新为:

'{"task_packets": [
    {
        "state": "DONE",
        "task_id": 1001
    },
    {
        "state": "REVERTING",
        "task_id": 1002
    }
]}'
Run Code Online (Sandbox Code Playgroud)

即我想使用指定的task_id内部 task_packets 数组更改值的状态。我建议以某种方式将jsonb_set()函数与#-运算符结合使用(首先从数组中删除值,然后使用更新的状态附加到它)。
我该怎么做?

Erw*_*ter 8

就像我评论的那样,使用规范化的数据库布局会更有效,像这样的表

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

除此之外,我们可以有一个 PK 约束来强制执行唯一的task_id数字。而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}]}';
Run Code Online (Sandbox Code Playgroud)

我建议使用LATERALjoin,其中包括排除多个匹配行在普通连接中可能错误地混在一起的可能性。

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}]}';
Run Code Online (Sandbox Code Playgroud)

同样可以用来实现相关子查询UPDATE(或在SELECT为好)。

为了使大表的速度更快,请确保有一个合适的index,最好是一个jsonb_path_ops索引: