如何用PostgreSQL更新jsonb字符串?

时间:2016-06-28 12:07:11

标签: json postgresql postgresql-9.4 jsonb nosql

我正在使用PostgreSQL 9.4.5。我想更新 jsonb列。

我的表格是这样构建的:

CREATE TABLE my_table (
  gid    serial PRIMARY KEY,
  "data" jsonb
);

JSON字符串是这样的:

{"files": [], "ident": {"id": 1, "country": null, "type ": "20"}}

以下SQL不执行此任务(语法错误 - SQL状态= 42601):

UPDATE my_table SET "data" -> 'ident' -> 'country' = 'Belgium';

有没有办法实现这个目标?

3 个答案:

答案 0 :(得分:1)

好的有两个功能:

create or replace function set_jsonb_value(p_j jsonb, p_key text, p_value jsonb) returns jsonb as $$
  select jsonb_object_agg(t.key, t.value) from (
    select 
      key, 
      case 
        when jsonb_typeof(value) = 'object' then set_jsonb_value(value, p_key, p_value)
        when key = p_key then p_value 
        else value 
      end as value from jsonb_each(p_j)) as t;
$$ language sql immutable;

首先只需更改现有密钥的值,而不管密钥路径如何:

postgres=# select set_jsonb_value(
  '{"files": [], "country": null, "ident": {"id": 1, "country": null, "type ": "20"}}', 
  'country', 
  '"foo"');
                                   set_jsonb_value                                    
--------------------------------------------------------------------------------------
 {"files": [], "ident": {"id": 1, "type ": "20", "country": "foo"}, "country": "foo"}
(1 row)


create or replace function set_jsonb_value(p_j jsonb, p_path text[], p_value jsonb) returns jsonb as $$
  select jsonb_object_agg(t.key, t.value) from (
    select 
      key, 
      case
        when jsonb_typeof(value) = 'object' then set_jsonb_value(value, p_path[2:1000], p_value)
        when key = p_path[1] then p_value 
        else value 
      end as value from jsonb_each(p_j)
    union all
    select 
      p_path[1],
      case 
        when array_length(p_path,1) = 1 then p_value 
        else set_jsonb_value('{}', p_path[2:1000], p_value) end 
    where not p_j ? p_path[1]) as t;
$$ language sql immutable;

第二个使用指定的路径更改现有密钥的值,或者如果路径不存在则创建它:

postgres=# select set_jsonb_value(
  '{"files": [], "country": null, "ident": {"id": 1, "type ": "20"}}', 
  '{ident,country}'::text[], 
  '"foo"');
                                   set_jsonb_value                                   
-------------------------------------------------------------------------------------
 {"files": [], "ident": {"id": 1, "type ": "20", "country": "foo"}, "country": null}
(1 row)

postgres=# select set_jsonb_value(
  '{"files": [], "country": null, "ident": {"id": 1, "type ": "20"}}', 
  '{ident,foo,bar,country}'::text[], 
  '"foo"');
                                            set_jsonb_value                                            
-------------------------------------------------------------------------------------------------------
 {"files": [], "ident": {"id": 1, "foo": {"bar": {"country": "foo"}}, "type ": "20"}, "country": null}
(1 row)

希望它对使用PostgreSQL的人有所帮助< 9.5
免责声明:在PostgreSQL 9.5上进行测试

答案 1 :(得分:0)

在PG 9.4中你运气不好"轻松"像jsonb_set()(9.5)这样的解决方案。您唯一的选择是解压缩JSON对象,进行更改并重新构建对象。听起来非常麻烦,确实如此:无论内置功能多么先进或精细,JSON操作起来都很糟糕。

CREATE TYPE data_ident AS (id integer, country text, "type" integer);

UPDATE my_table
SET "data" = json_build_object('files', "data"->'files', 'ident', ident.j)::jsonb
FROM (
    SELECT gid, json_build_object('id', j.id, 'country', 'Belgium', 'type', j."type") AS j
    FROM my_table
    JOIN LATERAL jsonb_populate_record(null::data_ident, "data"->'ident') j ON true) ident
WHERE my_table.gid = ident.gid;

SELECT子句中"data"->'ident'被解压缩到一个记录中(你需要CREATE TYPE一个结构)。然后使用新的国家/地区名称将其重新构建到JSON对象中。在UPDATE "ident"对象与"files"对象重新加入,整个事物转换为jsonb

纯粹的美丽事物 - 只要速度不是你的事情......

答案 2 :(得分:-1)

我之前的解决方案依赖于9.5功能。

我建议改为使用下面的abelisto解决方案或使用pl / perlu,plpythonu或plv8js以更好的支持语言编写json mutators。