How do I modify fields inside the PostgreSQL JSON

2015/07/29 15:36
阅读数 209

With postgresql 9.3 I can SELECT specific fields of a JSON data type, but how do you modify them using UPDATE? I can't find any examples of this in the postgresql documentation, or anywhere online. I have tried the obvious:

postgres=# create table test (data json); CREATE TABLE postgres=# insert into test (data) values ('{"a":1,"b":2}'); INSERT 0 1 postgres=# select data->'a' from test where data->>'b' = '2'; ?column? ---------- 1 (1 row) postgres=# update test set data->'a' = to_json(5) where data->>'b' = '2'; ERROR: syntax error at or near "->" LINE 1: update test set data->'a' = to_json(5) where data->>'b' = '2...
答案=============================


It is possible (without plpython or plv8) in pure SQL too (but needs 9.3+, will not work with 9.2)

CREATE OR REPLACE FUNCTION "json_object_set_key"( "json" json, "key_to_set" TEXT, "value_to_set" anyelement ) RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT AS $function$ SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}')::json FROM (SELECT * FROM json_each("json") WHERE "key" <> "key_to_set" UNION ALL SELECT "key_to_set", to_json("value_to_set")) AS "fields" $function$;

SQLFiddle

Edit:

A version, which sets multiple keys & values:

CREATE OR REPLACE FUNCTION "json_object_set_keys"( "json" json, "keys_to_set" TEXT[], "values_to_set" anyarray ) RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT AS $function$ SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}')::json FROM (SELECT * FROM json_each("json") WHERE "key" <> ALL ("keys_to_set") UNION ALL SELECT DISTINCT ON ("keys_to_set"["index"]) "keys_to_set"["index"], CASE WHEN "values_to_set"["index"] IS NULL THEN 'null'::json ELSE to_json("values_to_set"["index"]) END FROM generate_subscripts("keys_to_set", 1) AS "keys"("index") JOIN generate_subscripts("values_to_set", 1) AS "values"("index") USING ("index")) AS "fields" $function$;

Edit 2: as @ErwinBrandstetter noted these functions above works like a so-called UPSERT (updates a field if it exists, inserts if it does not exist). Here is a variant, which only UPDATE:

CREATE OR REPLACE FUNCTION "json_object_update_key"( "json" json, "key_to_set" TEXT, "value_to_set" anyelement ) RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT AS $function$ SELECT CASE WHEN ("json" -> "key_to_set") IS NULL THEN "json" ELSE (SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}') FROM (SELECT * FROM json_each("json") WHERE "key" <> "key_to_set" UNION ALL SELECT "key_to_set", to_json("value_to_set")) AS "fields")::json END $function$;

Edit 3: Here is recursive variant, which can set (UPSERT) a leaf value (and uses the first function from this answer), located at a key-path (where keys can only refer to inner objects, inner arrays not supported):

CREATE OR REPLACE FUNCTION "json_object_set_path"( "json" json, "key_path" TEXT[], "value_to_set" anyelement ) RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT AS $function$ SELECT CASE COALESCE(array_length("key_path", 1), 0) WHEN 0 THEN to_json("value_to_set") WHEN 1 THEN "json_object_set_key"("json", "key_path"[l], "value_to_set") ELSE "json_object_set_key"( "json", "key_path"[l], "json_object_set_path"( COALESCE(NULLIF(("json" -> "key_path"[l])::text, 'null'), '{}')::json, "key_path"[l+1:u], "value_to_set" ) ) END FROM array_lower("key_path", 1) l, array_upper("key_path", 1) u $function$;

Update: functions are compacted now.

=====================

To build upon @pozs's answers, here are a couple more PostgreSQL functions which may be useful to some. (Requires PostgreSQL 9.3+)

Delete By Key: Deletes a value from JSON structure by key.

CREATE OR REPLACE FUNCTION "json_object_del_key"( "json" json, "key_to_del" TEXT ) RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT AS $function$ SELECT CASE WHEN ("json" -> "key_to_del") IS NULL THEN "json" ELSE (SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}') FROM (SELECT * FROM json_each("json") WHERE "key" <> "key_to_del" ) AS "fields")::json END $function$;

Recursive Delete By Key: Deletes a value from JSON structure by key-path. (requires @pozs's json_object_set_key function)

CREATE OR REPLACE FUNCTION "json_object_del_path"( "json" json, "key_path" TEXT[] ) RETURNS json
  LANGUAGE sql
  IMMUTABLE
  STRICT AS $function$ SELECT CASE WHEN ("json" -> "key_path"[l] ) IS NULL THEN "json" ELSE CASE COALESCE(array_length("key_path", 1), 0) WHEN 0 THEN "json" WHEN 1 THEN "json_object_del_key"("json", "key_path"[l]) ELSE "json_object_set_key"( "json", "key_path"[l], "json_object_del_path"( COALESCE(NULLIF(("json" -> "key_path"[l])::text, 'null'), '{}')::json, "key_path"[l+1:u] ) ) END END FROM array_lower("key_path", 1) l, array_upper("key_path", 1) u $function$;

Usage examples:

s1=# SELECT json_object_del_key ('{"hello":[7,3,1],"foo":{"mofu":"fuwa", "moe":"kyun"}}', 'foo'), json_object_del_path('{"hello":[7,3,1],"foo":{"mofu":"fuwa", "moe":"kyun"}}', '{"foo","moe"}'); json_object_del_key | json_object_del_path ---------------------+----------------------------------------- {"hello":[7,3,1]} | {"hello":[7,3,1],"foo":{"mofu":"fuwa"}}

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部