PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como modifico campos dentro do novo tipo de dados JSON do PostgreSQL?


Atualizar :Com o PostgreSQL 9.5, existem alguns jsonb funcionalidade de manipulação dentro do próprio PostgreSQL (mas nenhuma para json; casts são necessários para manipular json valores).

Mesclando 2 (ou mais) objetos JSON (ou concatenando arrays):
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]'

Então, definindo uma chave simples pode ser feito usando:
SELECT jsonb '{"a":1}' || jsonb_build_object('<key>', '<value>')

Onde <key> deve ser string e <value> pode ser qualquer tipo to_jsonb() aceita.

Para definir um valor profundo em uma hierarquia JSON , o jsonb_set() função pode ser usada:
SELECT jsonb_set('{"a":[null,{"b":[]}]}', '{a,1,b,0}', jsonb '{"c":3}')
-- will yield jsonb '{"a":[null,{"b":[{"c":3}]}]}'

Lista completa de parâmetros de jsonb_set() :
jsonb_set(target         jsonb,
          path           text[],
          new_value      jsonb,
          create_missing boolean default true)

path também pode conter índices de matriz JSON e inteiros negativos que aparecem lá contam a partir do final de matrizes JSON. No entanto, um índice de matriz JSON inexistente, mas positivo, anexará o elemento ao final da matriz:
SELECT jsonb_set('{"a":[null,{"b":[1,2]}]}', '{a,1,b,1000}', jsonb '3', true)
-- will yield jsonb '{"a":[null,{"b":[1,2,3]}]}'

Para inserir na matriz JSON (enquanto preserva todos os valores originais) , o jsonb_insert() pode ser usada (em 9.6+; esta função apenas, nesta seção ):
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]}]}'

Lista completa de parâmetros de jsonb_insert() :
jsonb_insert(target       jsonb,
             path         text[],
             new_value    jsonb,
             insert_after boolean default false)

Novamente, inteiros negativos que aparecem em path contagem a partir do final das matrizes JSON.

Então, f.ex. anexar a um final de uma matriz JSON pode ser feito com:
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

No entanto, esta função está funcionando um pouco diferente (do que jsonb_set() ) quando o path em target é a chave de um objeto JSON. Nesse caso, ele só adicionará um novo par chave-valor para o objeto JSON quando a chave não for usada. Se for usado, ele irá gerar um erro:
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

Excluindo uma chave (ou um índice) de um objeto JSON (ou, de um array) pode ser feito com o - operador:
SELECT jsonb '{"a":1,"b":2}' - 'a', -- will yield jsonb '{"b":2}'
       jsonb '["a",1,"b",2]' - 1    -- will yield jsonb '["a","b",2]'

Exclusão, do fundo de uma hierarquia JSON pode ser feito com o #- operador:
SELECT '{"a":[null,{"b":[3.14]}]}' #- '{a,1,b,0}'
-- will yield jsonb '{"a":[null,{"b":[]}]}'

Para 9.4 , você pode usar uma versão modificada da resposta original (abaixo), mas em vez de agregar uma string JSON, você pode agregar em um objeto json diretamente com json_object_agg() .

Resposta original :É possível (sem plpython ou plv8) em SQL puro também (mas precisa de 9.3+, não funcionará com 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

Editar :

Uma versão, que define várias chaves e valores:
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$;

Editar 2 :como @ErwinBrandstetter observou, essas funções acima funcionam como um chamado UPSERT (atualiza um campo se existir, insere se não existir). Aqui está uma variante, que apenas 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$;

Editar 3 :Aqui está a variante recursiva, que pode definir (UPSERT ) um valor de folha (e usa a primeira função desta resposta), localizado em um caminho de chave (onde as chaves podem se referir apenas a objetos internos, matrizes internas não suportadas):
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$;

Atualizado:Adicionada função para substituir a chave de um campo json existente por outra chave fornecida. Pode ser útil para atualizar tipos de dados em migrações ou outros cenários, como alteração de estrutura de dados.
CREATE OR REPLACE FUNCTION json_object_replace_key(
    json_value json,
    existing_key text,
    desired_key text)
  RETURNS json AS
$BODY$
SELECT COALESCE(
(
    SELECT ('{' || string_agg(to_json(key) || ':' || value, ',') || '}')
    FROM (
        SELECT *
        FROM json_each(json_value)
        WHERE key <> existing_key
        UNION ALL
        SELECT desired_key, json_value -> existing_key
    ) AS "fields"
    -- WHERE value IS NOT NULL (Actually not required as the string_agg with value's being null will "discard" that entry)

),
    '{}'
)::json
$BODY$
  LANGUAGE sql IMMUTABLE STRICT
  COST 100;

Atualizar :as funções estão compactadas agora.