PostgreSQL 11+
Se você já estiver no PostgreSQL v. 11 (por causa do novo
JSONB
suporte para conversão de tipo
) sua melhor aposta provavelmente seria uma função personalizada escrita em Perl ou Python. Como sou a favor do Python 3, aqui está um exemplo funcional:
CREATE OR REPLACE FUNCTION jsonb_replace_in_array (val jsonb, path_to_array text[], replacement jsonb, entry_filters jsonb)
RETURNS jsonb
TRANSFORM FOR TYPE jsonb
LANGUAGE plpython3u
AS $$
v_new = val
tmp = v_new
for e in path_to_array:
tmp = tmp[e]
for item in tmp:
if (entry_filters is None or entry_filters.items() <= item.items()):
item.update(replacement)
return v_new
$$;
...que então pode ser usado da seguinte forma:
UPDATE configuration
SET
config = jsonb_replace_in_array(
config,
'{data}',
'{"value":"changed"}'::jsonb,
'{"oid":"11.5.15.1.4","instance":"1.1.4"}'::jsonb
)
WHERE config->'data' @> '[{"oid":"11.5.15.1.4","instance":"1.1.4"}]';
Então, sim, a condição é duplicada, mas apenas para limitar a quantidade de linhas a serem tocadas em primeiro lugar.
Para realmente trabalhar em uma instalação simples do PostgreSQL 11, você precisa das extensões
plpython3u
e jsonb_plpython3u
:CREATE EXTENSION plpython3u;
CREATE EXTENSION jsonb_plpython3u;
A lógica do Python explicada:
for e in path_to_array:
tmp = tmp[e]
...nos leva ao array de entradas que precisamos ver.
for item in tmp:
if (entry_filters is None or entry_filters.items() <= item.items()):
item.update(replacement)
...para cada item no array, verificamos se o critério do filtro é
null
(entry_filters is None
=corresponde a qualquer entrada) ou se a entrada "contém" o exemplo fornecido, incluindo chaves e valores (entry_filters.items() <= item.items()
). Se a entrada corresponder, substitua/adicione o conteúdo pela substituição fornecida.
Espero que vá na direção que você procura.
Olhando para os recursos atuais do PostgreSQL relacionados à modificação de JSON, seria muito complexo (se não complicado) e introduziria muita sobrecarga para fazer o mesmo com SQL puro.
PostgreSQL 9.6+
Caso você ainda não tenha a versão 11 disponível, a função a seguir fará o mesmo às custas de lidar com as conversões de tipo por si mesma, mas a manterá totalmente compatível com a API, então você, uma vez atualizado, a única coisa que você precisa fazer é substituir a função (não é necessário alterar nenhuma instrução usando esta função):
CREATE OR REPLACE FUNCTION jsonb_replace_in_array (val jsonb, path_to_array text[], replacement jsonb, entry_filters jsonb)
RETURNS jsonb
LANGUAGE plpython3u
AS $$
import json
v_new = json.loads(val)
t_replace = json.loads(replacement)
t_filters = json.loads(entry_filters)
tmp = v_new
for e in path_to_array:
tmp = tmp[e]
for item in tmp:
if (entry_filters is None or t_filters.items() <= item.items()):
item.update(t_replace)
return json.dumps(v_new)
$$;