我的端点,接受客户端请求 HTTP 方法 PATCH,JSON 合并补丁 (RFC 7396) 的有效负载内容类型。 https://www.rfc-editor.org/rfc/rfc7396
我们使用Oracle,更新数据库中的json内容非常方便,使用函数json_merge_patch()
UPDATE table_name SET po_document =
json_mergepatch(po_document, json_by_rfc7396);
我在Postgres中还没有找到类似的功能,
jsonb_set()
和运算符||
和#-
,不方便深度修补json内容。
深度修补 json 内容的 PostgreSQL 最佳实践是什么?
示例:
SELECT json_merge_patch(
'{"root": {"k1": "v1", "k2": "v2"} }'::jsonb, -- source JSON
'{"root": {"k1": "upd", "k2": null, "k3": "new"} }'::jsonb -- JSON patch (RFC 7396)
)
输出
{"root": {"k1": "upd","k3": "new"} }
留下我的 2 美分,以获得更紧凑的解决方案,基于 这篇文章:
CREATE OR REPLACE FUNCTION json_merge_patch("target" jsonb, "patch" jsonb) RETURNS jsonb AS $$
BEGIN
RETURN COALESCE(jsonb_object_agg(
COALESCE("tkey", "pkey"),
CASE
WHEN "tval" ISNULL THEN "pval"
WHEN "pval" ISNULL THEN "tval"
WHEN jsonb_typeof("tval") != 'object' OR jsonb_typeof("pval") != 'object' THEN "pval"
ELSE json_merge_patch("tval", "pval")
END
), '{}'::jsonb)
FROM jsonb_each("target") e1("tkey", "tval")
FULL JOIN jsonb_each("patch") e2("pkey", "pval")
ON "tkey" = "pkey"
WHERE jsonb_typeof("pval") != 'null'
OR "pval" ISNULL;
END;
$$ LANGUAGE plpgsql;
就我而言,它遵循 RFC 7396。
该规范足够简单,可以遵循递归。
create or replace function jsonb_merge_patch(v_basedoc jsonb, v_patch jsonb)
returns jsonb as $$
with recursive patchexpand as(
select '{}'::text[] as jpath, v_patch as jobj, jsonb_typeof(v_patch) as jtype, 0 as lvl
union all
select p.jpath||o.key as jpath, p.jobj->o.key as jobj, jsonb_typeof(p.jobj->o.key) as jtype, p.lvl + 1 as lvl
from patchexpand p
cross join lateral jsonb_each(case when p.jtype = 'object' then p.jobj else '{}'::jsonb end) as o(key, value)
), pathnum as (
select *, row_number() over (order by lvl, jpath) as rn
from patchexpand
), apply as (
select case
when jsonb_typeof(v_basedoc) = 'object' then v_basedoc
else '{}'::jsonb
end as basedoc,
p.rn
from pathnum p
where p.rn = 1
union all
select case
when p.jtype = 'object' then a.basedoc
when p.jtype = 'null' then a.basedoc #- p.jpath
else jsonb_set(a.basedoc, p.jpath, p.jobj)
end as basedoc,
p.rn
from apply a
join pathnum p
on p.rn = a.rn + 1
)
select case
when jsonb_typeof(v_patch) != 'object' then v_patch
else basedoc
end
from apply
order by rn desc
limit 1;
$$
language sql;
使用 RFC 中的示例进行测试:
select jsonb_pretty(jsonb_merge_patch('{
"title": "Goodbye!",
"author" : {
"givenName" : "John",
"familyName" : "Doe"
},
"tags":[ "example", "sample" ],
"content": "This will be unchanged"
}'::jsonb,
'{
"title": "Hello!",
"phoneNumber": "+01-123-456-7890",
"author": {
"familyName": null
},
"tags": [ "example" ]
}'::jsonb));
jsonb_pretty
------------------------------------------
{ +
"tags": [ +
"example" +
], +
"title": "Hello!", +
"author": { +
"givenName": "John" +
}, +
"content": "This will be unchanged",+
"phoneNumber": "+01-123-456-7890" +
}
(1 row)
用您问题中的示例进行测试:
SELECT jsonb_merge_patch(
'{"root": {"k1": "v1", "k2": "v2"} }'::jsonb, -- source JSON
'{"root": {"k1": "upd", "k2": null, "k3": "new"} }'::jsonb -- JSON patch (RFC 7396)
);
jsonb_merge_patch
--------------------------------------
{"root": {"k1": "upd", "k3": "new"}}
(1 row)
对于 JSONB 字段,您可以使用 concatenation 操作
||
。
这是我制作的用于通过合并字段进行更新插入的示例:
INSERT INTO table_name (id, title, po_document)
VALUES ($1, $2, $3::JSONB)
ON CONFLICT (id) DO
UPDATE
SET title = COALESCE(NULLIF($2, ''), table_name.title),
props = table_name.props || $3::JSONB
这里我有一个表,其中包含文档 ID、标题及其 JSONB 格式的属性。 创建文档时,我可以传递所有 3 个值。 要更新唯一的标题,我可以仅在第二个
title
参数中传递它,并将第三个 props
参数设置为空对象 {}
。
要更新属性,我可以将第二个参数 title
留空(然后当前值将保留),并使用仅包含应更新字段的对象设置第三个参数。
要删除字段值,您可以将其显式设置为 null