如何使用 where 子句修改或删除存储在 PostgreSQL 中 jsonb 列类型中的 JSON 数组中的特定 JSON 对象?

How to modify or remove a specific JSON object from JSON array stored in jsonb column type in PostgreSQL using where clause?

在我的 Postgres 数据库中,我有一个 table 列具有 jsonb 数据类型。在该列中,我存储了 JSON 数组。现在,我想删除或修改数组中的特定 JSON 对象。

我的 JSON 数组看起来像

[
    {
        "ModuleId": 1,
        "ModuleName": "XYZ"
    },
    {
        "ModuleId": 2,
        "ModuleName": "ABC"
    }
]

现在,我要执行两个操作:

  1. 如何从上述数组中删除 ModuleId 为 1 的 JSON 对象?
  2. 如何修改 JSON 对象,即将 ModuleName 更改为 'CBA',其 ModuleId 为 1?

有没有一种方法可以直接在 JSON 数组上执行查询?

注:Postgres 版本为 12.0

这两个问题都需要取消嵌套并聚合回(修改后的)JSON 元素。对于这两个问题,我会创建一个函数以使其更易于使用。

create function remove_element(p_value jsonb, p_to_remove jsonb)
  returns jsonb
as
$$
  select jsonb_agg(t.element order by t.idx)  
  from jsonb_array_elements(p_value) with ordinality as t(element, idx)
  where not t.element @> p_to_remove;
$$
language sql
immutable;

函数可以这样使用,例如在更新语句中:

update the_table
  set the_column = remove_element(the_column, '{"ModuleId": 1}')
where ...

对于第二个问题,类似的函数就派上用场了。

create function change_value(p_value jsonb, p_what jsonb, p_new jsonb)
  returns jsonb
as
$$
  select jsonb_agg(
         case
           when t.element @> p_what then t.element||p_new
           else t.element
         end order by t.idx)  
  from jsonb_array_elements(p_value) with ordinality as t(element, idx);
$$
language sql
immutable;

|| 运算符将覆盖现有密钥,因此这有效地将旧名称替换为新名称。

你可以这样使用它:

update the_table
  set the_column = change_value(the_column, '{"ModuleId": 1}', '{"ModuleName": "CBA"}')
where ...;

我认为传递 JSON 值比硬编码键更灵活一些,这使得函数的使用非常有限。第一个函数也可用于通过比较多个键来删除数组元素。


如果您不想创建函数,请将函数调用替换为函数中的 select

对于这两种情况,请考虑使用包含动态逻辑的子查询来确定包含 ModuleId 键值等于 1 的元素的索引。

对于第一种情况,使用#-运算符:

WITH s AS
(
 SELECT ('{'||idx-1||'}')::text[] AS path, jsdata
   FROM tab 
  CROSS JOIN jsonb_array_elements(jsdata)
   WITH ORDINALITY arr(j,idx)
  WHERE j->>'ModuleId'='1' 
)
UPDATE tab
   SET jsdata = s.jsdata #- path
  FROM s  

对于第二种情况,使用 jsonb_set() 函数和来自子查询的路径:

WITH s AS
(
 SELECT ('{'||idx-1||',ModuleId}')::text[] AS path
   FROM tab 
  CROSS JOIN jsonb_array_elements(jsdata) 
   WITH ORDINALITY arr(j,idx)
  WHERE j->>'ModuleId'='1' 
)
UPDATE tab
   SET jsdata = jsonb_set(jsdata,s.path,'"CBA"',false)
  FROM s

Demo