PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come posso modificare i campi all'interno del nuovo tipo di dati JSON di PostgreSQL?

Aggiorna :Con PostgreSQL 9.5, ci sono alcuni jsonb funzionalità di manipolazione all'interno di PostgreSQL stesso (ma nessuna per json; i cast sono necessari per manipolare json valori).

Unire 2 (o più) oggetti JSON (o concatenare array):

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]'

Quindi, impostare una chiave semplice può essere fatto utilizzando:

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

Dove <key> dovrebbe essere stringa e <value> può essere di qualsiasi tipo to_jsonb() accetta.

Per impostare un valore all'interno di una gerarchia JSON , il jsonb_set() può essere utilizzata la funzione:

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

Elenco completo dei parametri di jsonb_set() :

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

path può contenere anche indici di array JSON e numeri interi negativi che appaiono lì contano dalla fine degli array JSON. Tuttavia, un indice di matrice JSON non esistente ma positivo aggiungerà l'elemento alla fine della matrice:

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

Per l'inserimento nell'array JSON (conservando tutti i valori originali) , il jsonb_insert() può essere utilizzata (in 9.6+; solo questa funzione, in questa sezione ):

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]}]}'

Elenco completo dei parametri di jsonb_insert() :

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

Di nuovo, numeri interi negativi che appaiono in path contare dalla fine degli array JSON.

Quindi, f.ex. l'aggiunta all'estremità di un array JSON può essere eseguita con:

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

Tuttavia, questa funzione funziona in modo leggermente diverso (rispetto a jsonb_set() ) quando il path in target è la chiave di un oggetto JSON. In tal caso, aggiungerà solo una nuova coppia chiave-valore per l'oggetto JSON quando la chiave non viene utilizzata. Se viene utilizzato, genererà un errore:

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

Eliminazione di una chiave (o di un indice) da un oggetto JSON (o, da un array) può essere fatto con - operatore:

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

Eliminazione, da una gerarchia JSON profonda può essere fatto con il #- operatore:

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

Per 9.4 , puoi utilizzare una versione modificata della risposta originale (sotto), ma invece di aggregare una stringa JSON, puoi aggregarla in un oggetto json direttamente con json_object_agg() .

Risposta originale :È possibile (senza plpython o plv8) anche in puro SQL (ma necessita di 9.3+, non funzionerà con 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

Modifica :

Una versione che imposta più chiavi e valori:

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

Modifica 2 :come ha notato @ErwinBrandstetter, queste funzioni sopra funzionano come un cosiddetto UPSERT (aggiorna un campo se esiste, lo inserisce se non esiste). Ecco una variante, che solo 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$;

Modifica 3 :Ecco una variante ricorsiva, che può impostare (UPSERT ) un valore foglia (e utilizza la prima funzione di questa risposta), situato in un percorso chiave (dove le chiavi possono fare riferimento solo a oggetti interni, array interni non supportati):

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

Aggiornato:aggiunta la funzione per sostituire la chiave di un campo json esistente con un'altra chiave specificata. Può essere utile per aggiornare i tipi di dati nelle migrazioni o in altri scenari come la modifica della struttura dei dati.

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;

Aggiorna :le funzioni sono ora compattate.