Как изменить поля внутри нового типа данных 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 ответов


обновление: С 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_insert() функция может использоваться (в 9.6+; эта функция только в этот раздел):

SELECT jsonb_insert('{"a":[null,{"b":[1]}]}', '{a,1,b,0}', jsonb '2')
-- will yield jsonb '{"a":[null,{"b":[2,1]}]}', and
SELECT jsonb_insert('{"a":[null,{"b":[1]}]}', '{a,1,b,0}', jsonb '2', true)
-- will yield jsonb '{"a":[null,{"b":[1,2]}]}'

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

jsonb_insert(target       jsonb,
             path         text[],
             new_value    jsonb,
             insert_after boolean default false)

опять же, отрицательные целые числа, которые появляются в path отсчет от конца массивов JSON.

Итак, f.бывший. добавление к концу массива JSON можно сделать с помощью:

SELECT jsonb_insert('{"a":[null,{"b":[1,2]}]}', '{a,1,b,-1}', jsonb '3', true)
-- will yield jsonb '{"a":[null,{"b":[1,2,3]}]}', and
этот функция работает несколько иначе (чем 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 can покончим с #- оператор:

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

редактировать:

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

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

Изменить 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. обновляет поле
  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 type) для 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, вам просто нужно typecast to:: 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);

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