Postgres中有一个与Oracle类似的函数json_merge_patch

问题描述

我的端点,接受客户端请求HTTP方法PATCH,这是JSON Merge Patch(RFC 7396)的有效内容类型。 https://tools.ietf.org/html/rfc7396

我们使用了Oracle,使用函数 json_merge_patch ()

可以很方便地更新数据库中的json内容
UPDATE table_name SET po_document =
  json_mergepatch(po_document,json_by_rfc7396);

https://docs.oracle.com/en/database/oracle/oracle-database/19/adjsn/updating-json-document-json-merge-patch.html

我没有在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"} }

解决方法

该规范非常简单,可以进行递归操作。

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)

,

根据this post,在这里留下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