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$;
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('{', '}')::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 @ErwinBrandstetternotedthese functions above works like a so-calledUPSERT(updates a field if it exists,inserts if it does not exist). Here is a variant,which onlyUPDATE:
CREATE OR REPLACE FUNCTION "json_object_update_key"( "json" json, "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('{', '}') 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", "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('{', '}') 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'sjson_object_set_keyfunction)
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", 0) WHEN 0 THEN "json" WHEN 1 THEN "json_object_del_key"("json", "key_path"[l]) ELSE "json_object_set_key"( "json", "json_object_del_path"( COALESCE(NULLIF(("json" -> "key_path"[l])::text, "key_path"[l+1:u] ) ) END END FROM array_lower("key_path",sans-serif;background-color:#FFFFFF;"> Usage examples:s1=# SELECT json_object_del_key ('{"hello":[7,3,1],"foo":{"mofu":"fuwa","moe":"kyun"}}', 'foo'), json_object_del_path('{"hello":[7, '{"foo","moe"}'); json_object_del_key | json_object_del_path ---------------------+----------------------------------------- {"hello":[7,3,1]} | {"hello":[7,1],"foo":{"mofu":"fuwa"}}