Как изменить поля внутри нового типа данных PostgreSQL JSON?


с postgresql 9.3 я могу выбрать определенные поля типа данных JSON, но как вы их изменяете с помощью UPDATE? Я не могу найти никаких примеров этого в документации postgresql или где-либо в интернете. Я попробовал очевидное:

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...
13 146

13 ответов:

обновление:С PostgreSQL 9.5 есть немного jsonb функциональность манипуляции в самом PostgreSQL (но не для json; броски необходимы для управления json значения).

объединение 2 (или более) объектов JSON (или объединение массивов):

SELECT jsonb '{"a":1}' || jsonb '{"b":2}', -- will yield jsonb '{"a":1,"b":2}'
       jsonb '["a",1]' || jsonb '["b",2]'  -- will yield jsonb '["a",1,"b",2]'

и установка простого ключа можно сделать с помощью:

SELECT jsonb '{"a":1}' || jsonb_build_object('<key>', '<value>')

здесь <key> должна быть строка, а <value> может быть любой тип to_jsonb() можно.

на установка значения глубоко в иерархии JSON на jsonb_set() функция может быть использована:

SELECT jsonb_set('{"a":[null,{"b":[]}]}', '{a,1,b,0}', jsonb '{"c":3}')
-- will yield jsonb '{"a":[null,{"b":[{"c":3}]}]}'

полный параметр list jsonb_set():

jsonb_set(target         jsonb,
          path           text[],
          new_value      jsonb,
          create_missing boolean default true)

path также может содержать индексы массива JSON и отрицательные целые числа, которые появляются там, отсчитываются от конца массивов JSON. Однако несуществующий, но положительный индекс массива JSON добавит элемент в конец массива:

SELECT jsonb_set('{"a":[null,{"b":[1,2]}]}', '{a,1,b,1000}', jsonb '3', true)
-- will yield jsonb '{"a":[null,{"b":[1,2,3]}]}'

для вставка в массив JSON (при сохранении всех исходных значений) на этот функция работает немного по-другому (чем jsonb_set()) если path на target - это ключ объекта JSON. В этом случае он только добавит новую пару ключ-значение для объекта JSON, когда ключ не используется. Если он используется, он вызовет ошибку:

SELECT jsonb_insert('{"a":[null,{"b":[1]}]}', '{a,1,c}', jsonb '[2]')
-- will yield jsonb '{"a":[null,{"b":[1],"c":[2]}]}', but
SELECT jsonb_insert('{"a":[null,{"b":[1]}]}', '{a,1,b}', jsonb '[2]')
-- will raise SQLSTATE 22023 (invalid_parameter_value): cannot replace existing key

удаление ключа (или индекса) из объекта JSON (или из массива) можно сделать с помощью - оператор:

SELECT jsonb '{"a":1,"b":2}' - 'a', -- will yield jsonb '{"b":2}'
       jsonb '["a",1,"b",2]' - 1    -- will yield jsonb '["a","b",2]'

удаление, из глубины иерархии JSON может покончим с #- оператор:

SELECT '{"a":[null,{"b":[3.14]}]}' #- '{a,1,b,0}'
-- will yield jsonb '{"a":[null,{"b":[]}]}'

для 9.4, вы можете использовать измененную версию исходного ответа (ниже), но вместо агрегирования строки JSON вы можете агрегировать в объект json непосредственно с помощью json_object_agg().

оригинальный ответ: это возможно (без plpython или plv8) в чистом SQL тоже (но нужно 9.3+, не будет работать с 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:

версия, которая устанавливает несколько ключей и значений:

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: как @ErwinBrandstetter отметить эти функции выше работает как так называемый UPSERT (обновляет поле, если оно существует, вставляет, если оно не существует). Вот такой вариант, который только 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$;

редактировать 3: вот рекурсивный вариант, который можно задать (UPSERT) конечное значение (и использует первую функцию из этого ответа), расположенное на пути ключа (где ключи могут ссылаться только на внутренние объекты, внутренние массивы не поддерживаются):

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$;

обновление: функции теперь уплотняется.

С 9.5 использовать jsonb_set -

UPDATE objects
SET body = jsonb_set(body, '{name}', '"Mary"', true)
WHERE id = 1; 

где body-это тип столбца jsonb.

С Postgresql 9.5 это можно сделать следующим образом -

UPDATE test
SET data = data - 'a' || '{"a":5}'
WHERE data->>'b' = '2';

или

UPDATE test
SET data = jsonb_set(data, '{a}', '5'::jsonb);

кто-то спросил, как обновить сразу несколько полей в значении jsonb. Предположим, мы создаем таблицу:

CREATE TABLE testjsonb ( id SERIAL PRIMARY KEY, object JSONB );

затем вставляем экспериментальную строку:

INSERT INTO testjsonb
VALUES (DEFAULT, '{"a":"one", "b":"two", "c":{"c1":"see1","c2":"see2","c3":"see3"}}');

затем мы обновляем строку:

UPDATE testjsonb SET object = object - 'b' || '{"a":1,"d":4}';

который делает следующее:

  1. обновляет поле a
  2. удаляет b поле
  3. добавить поле d

выбор данные:

SELECT jsonb_pretty(object) FROM testjsonb;

в результате:

      jsonb_pretty
-------------------------
 {                      +
     "a": 1,            +
     "c": {             +
         "c1": "see1",  +
         "c2": "see2",  +
         "c3": "see3",  +
     },                 +
     "d": 4             +
 }
(1 row)

чтобы обновить поле внутри, не используйте оператор concat ||. Использовать вместо jsonb_set. Что не так просто:

UPDATE testjsonb SET object =
jsonb_set(jsonb_set(object, '{c,c1}','"seeme"'),'{c,c2}','"seehim"');

используя оператор concat для {c, c1} например:

UPDATE testjsonb SET object = object || '{"c":{"c1":"seedoctor"}}';

удалит {c,c2} и {c, c3}.

для большей мощности, искать власть в функции postgresql json документация. Можно было бы заинтересовать #- оператор jsonb_set функции, а также

чтобы построить на ответах @pozs, вот еще несколько функций PostgreSQL, которые могут быть полезны для некоторых. (Требуется Версия PostgreSQL 9.3+)

Удалить По Ключу: удаляет значение из структуры JSON по ключу.

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$;

Рекурсивное Удаление По Ключу: удаляет значение из структуры JSON по пути ключа. (требуется @pozs это )

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$;

примеры использования:

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"}}
UPDATE test
SET data = data::jsonb - 'a' || '{"a":5}'::jsonb
WHERE data->>'b' = '2'

Это, кажется, работает на PostgreSQL 9.5

С PostgreSQL 9.4 мы реализовали следующую функцию python. Он также может работать с PostgreSQL 9.3.

create language plpython2u;

create or replace function json_set(jdata jsonb, jpaths jsonb, jvalue jsonb) returns jsonb as $$
import json

a = json.loads(jdata)
b = json.loads(jpaths)

if a.__class__.__name__ != 'dict' and a.__class__.__name__ != 'list':
  raise plpy.Error("The json data must be an object or a string.")

if b.__class__.__name__ != 'list':
   raise plpy.Error("The json path must be an array of paths to traverse.")

c = a
for i in range(0, len(b)):
  p = b[i]
  plpy.notice('p == ' + str(p))

  if i == len(b) - 1:
    c[p] = json.loads(jvalue)

  else:
    if p.__class__.__name__ == 'unicode':
      plpy.notice("Traversing '" + p + "'")
      if c.__class__.__name__ != 'dict':
        raise plpy.Error("  The value here is not a dictionary.")
      else:
        c = c[p]

    if p.__class__.__name__ == 'int':
      plpy.notice("Traversing " + str(p))
      if c.__class__.__name__ != 'list':
        raise plpy.Error("  The value here is not a list.")
      else:
        c = c[p]

    if c is None:
      break    

return json.dumps(a)
$$ language plpython2u ;

пример использования:

create table jsonb_table (jsonb_column jsonb);
insert into jsonb_table values
('{"cars":["Jaguar", {"type":"Unknown","partsList":[12, 34, 56]}, "Atom"]}');

select jsonb_column->'cars'->1->'partsList'->2, jsonb_column from jsonb_table;

update jsonb_table
set jsonb_column = json_set(jsonb_column, '["cars",1,"partsList",2]', '99');

select jsonb_column->'cars'->1->'partsList'->2, jsonb_column from jsonb_table;

обратите внимание, что для предыдущего работодателя я написал набор функций C для управления данными JSON в виде текста (а не как json или jsonb тип) для PostgreSQL 7, 8 и 9. Например, извлечение данных с помощью json_path('{"obj":[12, 34, {"num":-45.67}]}', '$.obj[2]['num']'), установочные данные с json_path_set('{"obj":[12, 34, {"num":-45.67}]}', '$.obj[2]['num']', '99.87') и так далее. Это заняло около 3 дней работы, так что если вам нужно его запустить унаследованные системы и есть время, чтобы сэкономить, это может стоить усилий. Я предполагаю, что версия C намного быстрее, чем версия python.

даже если следующее не удовлетворит этот запрос (функция json_object_agg недоступна в PostgreSQL 9.3), следующее Может быть полезно для тех, кто ищет оператор / / для PostgreSQL 9.4, как реализовано в предстоящем PostgreSQL 9.5:

CREATE OR REPLACE FUNCTION jsonb_merge(left JSONB, right JSONB)
RETURNS JSONB
AS $$
SELECT
  CASE WHEN jsonb_typeof() = 'object' AND jsonb_typeof() = 'object' THEN
       (SELECT json_object_agg(COALESCE(o.key, n.key), CASE WHEN n.key IS NOT NULL THEN n.value ELSE o.value END)::jsonb
        FROM jsonb_each() o
        FULL JOIN jsonb_each() n ON (n.key = o.key))
   ELSE 
     (CASE WHEN jsonb_typeof() = 'array' THEN LEFT(::text, -1) ELSE '['||::text END ||', '||
      CASE WHEN jsonb_typeof() = 'array' THEN RIGHT(::text, -1) ELSE ::text||']' END)::jsonb
   END     
$$ LANGUAGE sql IMMUTABLE STRICT;
GRANT EXECUTE ON FUNCTION jsonb_merge(jsonb, jsonb) TO public;
CREATE OPERATOR || ( LEFTARG = jsonb, RIGHTARG = jsonb, PROCEDURE = jsonb_merge );

Я написал небольшую функцию для себя, что работает рекурсивно в Postgres 9.4. Вот функция (я надеюсь, что она хорошо работает для вас):

CREATE OR REPLACE FUNCTION jsonb_update(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    v RECORD;
BEGIN
    IF jsonb_typeof(val2) = 'null'
    THEN 
        RETURN val1;
    END IF;

    result = val1;

    FOR v IN SELECT key, value FROM jsonb_each(val2) LOOP

        IF jsonb_typeof(val2->v.key) = 'object'
            THEN
                result = result || jsonb_build_object(v.key, jsonb_update(val1->v.key, val2->v.key));
            ELSE
                result = result || jsonb_build_object(v.key, v.value);
        END IF;
    END LOOP;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

вот пример использования:

select jsonb_update('{"a":{"b":{"c":{"d":5,"dd":6},"cc":1}},"aaa":5}'::jsonb, '{"a":{"b":{"c":{"d":15}}},"aa":9}'::jsonb);
                            jsonb_update                             
---------------------------------------------------------------------
 {"a": {"b": {"c": {"d": 15, "dd": 6}, "cc": 1}}, "aa": 9, "aaa": 5}
(1 row)

Как вы можете видеть, он анализирует глубоко вниз и обновляет/добавляет значения там, где это необходимо.

к сожалению, я ничего не нашел в документации, но вы можете использовать некоторые обходные пути, например, вы можете написать некоторые расширенные функции.

например, в Python:

CREATE or REPLACE FUNCTION json_update(data json, key text, value json)
returns json
as $$
from json import loads, dumps
if key is None: return data
js = loads(data)
js[key] = value
return dumps(js)
$$ language plpython3u

а то

update test set data=json_update(data, 'a', to_json(5)) where data->>'b' = '2';

следующий фрагмент plpython может пригодиться.

CREATE EXTENSION IF NOT EXISTS plpythonu;
CREATE LANGUAGE plpythonu;

CREATE OR REPLACE FUNCTION json_update(data json, key text, value text)
 RETURNS json
 AS $$
    import json
    json_data = json.loads(data)
    json_data[key] = value
    return json.dumps(json_data, indent=4)
 $$ LANGUAGE plpythonu;

-- Check how JSON looks before updating

SELECT json_update(content::json, 'CFRDiagnosis.mod_nbs', '1')
FROM sc_server_centre_document WHERE record_id = 35 AND template = 'CFRDiagnosis';

-- Once satisfied update JSON inplace

UPDATE sc_server_centre_document SET content = json_update(content::json, 'CFRDiagnosis.mod_nbs', '1')
WHERE record_id = 35 AND template = 'CFRDiagnosis';

Если ваш тип поля имеет json следующее будет работать для вас.

UPDATE 
table_name
SET field_name = field_name::jsonb - 'key' || '{"key":new_val}' 
WHERE field_name->>'key' = 'old_value'.

оператор ' - ' удаляет пару ключ / значение или строковый элемент из левого операнда. Пары ключ/значение, сопоставляются на основе их ключевых значений.

оператор '| / ' объединяет два значения jsonb в новое значение jsonb.

поскольку это операторы jsonb, вам просто нужно набрать:: jsonb

Подробнее : функции и операторы JSON

вы можете прочитать мою записку здесь

вы также можете увеличить ключи атомарно в пределах jsonb такой:

UPDATE users SET counters = counters || CONCAT('{"bar":', COALESCE(counters->>'bar','0')::int + 1, '}')::jsonb WHERE id = 1;

SELECT * FROM users;

 id |    counters
----+------------
  1 | {"bar": 1}

неопределенный ключ - > принимает начальное значение 0.

для более подробного объяснения, см. Мой ответ здесь: https://stackoverflow.com/a/39076637

это сработало для меня, при попытке обновить поле типа строки.

UPDATE table_name 
SET body = jsonb_set(body, '{some_key}', to_json('value'::TEXT)::jsonb);

надеюсь, что это поможет кому-то еще!