PostgreSQL比较两个jsonb对象

PostgreSQL比较两个jsonb对象,postgresql,jsonb,postgresql-9.5,Postgresql,Jsonb,Postgresql 9.5,使用,格式提供了极好的机会。但现在我被一个看起来相对简单的操作困住了 比较两个jsonb对象;查看一个文档与另一个文档的不同之处或缺失之处 到目前为止我拥有的 WITH reports(id,DATA) AS ( VALUES (1,'{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb), (2,'{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb) ) SELECT jsonb

使用,格式提供了极好的机会。但现在我被一个看起来相对简单的操作困住了

比较两个jsonb对象;查看一个文档与另一个文档的不同之处或缺失之处

到目前为止我拥有的

WITH reports(id,DATA) AS (
          VALUES (1,'{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb),
                 (2,'{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb) )
SELECT jsonb_object_agg(anon_1.key, anon_1.value)
FROM
  (SELECT anon_2.key AS KEY,
      reports.data -> anon_2.KEY AS value
   FROM reports,
     (SELECT DISTINCT jsonb_object_keys(reports.data) AS KEY
      FROM reports) AS anon_2
   ORDER BY reports.id DESC) AS anon_1
应返回第1行与第2行的差值:

'{"b":"bbb", "c":"ccc", "d":null}'
相反,它还返回重复项(
{“a”:“aaa”}
)。也;总的来说,可能有一种更优雅的方法

更新 查询:

SELECT jsonb_diff_val(
    '{"a":"aaa", "b":"bbb", "c":"ccc"}'::jsonb,
    '{"a":"aaa", "b":"jjj", "d":"ddd"}'::jsonb
);
            jsonb_diff_val             
---------------------------------------
 {"b": "bbb", "c": "ccc", "d": "null"}
(1 row)

我创建了类似的函数,该函数将递归扫描对象,并返回新对象和旧对象之间的差异。我无法找到一种“更好”的方法来确定jsonb对象是否“为空”——因此,如果有任何建议可以简化它,我将不胜感激。我计划使用它来跟踪对jsonb对象的更新,所以我只存储已经更改的内容

以下是函数:

CREATE OR REPLACE FUNCTION jsonb_diff_val(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    object_result JSONB;
    i int;
    v RECORD;
BEGIN
    IF jsonb_typeof(val1) = 'null'
    THEN 
        RETURN val2;
    END IF;

    result = val1;
    FOR v IN SELECT * FROM jsonb_each(val1) LOOP
        result = result || jsonb_build_object(v.key, null);
    END LOOP;

    FOR v IN SELECT * FROM jsonb_each(val2) LOOP
        IF jsonb_typeof(val1->v.key) = 'object' AND jsonb_typeof(val2->v.key) = 'object'
        THEN
            object_result = jsonb_diff_val(val1->v.key, val2->v.key);
            -- check if result is not empty 
            i := (SELECT count(*) FROM jsonb_each(object_result));
            IF i = 0
            THEN 
                result = result - v.key; --if empty remove
            ELSE 
                result = result || jsonb_build_object(v.key,object_result);
            END IF;
        ELSIF val1->v.key = val2->v.key THEN 
            result = result - v.key;
        ELSE
            result = result || jsonb_build_object(v.key,v.value);
        END IF;
    END LOOP;

    RETURN result;

END;
$$ LANGUAGE plpgsql;
然后简单查询如下所示:

SELECT jsonb_diff_val(                                                                                                                                                                                                                                                           
    '{"a":"aaa", "b":{"b1":"b","b2":"bb","b3":{"b3a":"aaa","b3c":"ccc"}}, "c":"ccc"}'::jsonb,
    '{"a":"aaa", "b":{"b1":"b1","b3":{"b3a":"aaa","b3c":"cccc"}}, "d":"ddd"}'::jsonb
);
                                jsonb_diff_val                                 
-------------------------------------------------------------------------------
 {"b": {"b1": "b1", "b2": null, "b3": {"b3c": "cccc"}}, "c": null, "d": "ddd"}
(1 row)
SELECT * FROM jsonb_object_diff(
  '{"a": 1, "b": 5, "extra1": "woo", "old_null": null, "just_null": null}'::jsonb,
  '{"a": 1, "b": 4, "extra2": "ahoj", "new_null": null, "just_null": null}'::jsonb);

-[ RECORD 1 ]--------------------------------------
old  | {"b": 5, "extra1": "woo", "old_null": null}
new  | {"b": 4, "extra2": "ahoj", "new_null": null}
same | {"a": 1, "just_null": null}

我的解决方案不是递归的,但您可以使用它来检测公共键/值:

-- Diff two jsonb objects
CREATE TYPE jsonb_object_diff_result AS (
  old jsonb,
  new jsonb,
  same jsonb
);
CREATE OR REPLACE FUNCTION jsonb_object_diff(in_old jsonb, in_new jsonb)
RETURNS jsonb_object_diff_result AS
$jsonb_object_diff$
DECLARE
  _key text;
  _value jsonb;
  _old jsonb;
  _new jsonb;
  _same jsonb;
BEGIN
  _old := in_old;
  _new := in_new;

  FOR _key, _value IN SELECT * FROM jsonb_each(_old) LOOP
    IF (_new -> _key) = _value THEN
      _old := _old - _key;
      _new := _new - _key;
      IF _same IS NULL THEN
        _same := jsonb_build_object(_key, _value);
      ELSE
        _same := _same || jsonb_build_object(_key, _value);
      END IF;
    END IF;
  END LOOP;

  RETURN (_old, _new, _same);
END;
$jsonb_object_diff$
LANGUAGE plpgsql;
结果可以如下所示:

SELECT jsonb_diff_val(                                                                                                                                                                                                                                                           
    '{"a":"aaa", "b":{"b1":"b","b2":"bb","b3":{"b3a":"aaa","b3c":"ccc"}}, "c":"ccc"}'::jsonb,
    '{"a":"aaa", "b":{"b1":"b1","b3":{"b3a":"aaa","b3c":"cccc"}}, "d":"ddd"}'::jsonb
);
                                jsonb_diff_val                                 
-------------------------------------------------------------------------------
 {"b": {"b1": "b1", "b2": null, "b3": {"b3c": "cccc"}}, "c": null, "d": "ddd"}
(1 row)
SELECT * FROM jsonb_object_diff(
  '{"a": 1, "b": 5, "extra1": "woo", "old_null": null, "just_null": null}'::jsonb,
  '{"a": 1, "b": 4, "extra2": "ahoj", "new_null": null, "just_null": null}'::jsonb);

-[ RECORD 1 ]--------------------------------------
old  | {"b": 5, "extra1": "woo", "old_null": null}
new  | {"b": 4, "extra2": "ahoj", "new_null": null}
same | {"a": 1, "just_null": null}

这是一个没有创建新函数的解决方案

SELECT
    json_object_agg(COALESCE(old.key, new.key), old.value)
  FROM json_each_text('{"a":"aaa", "b":"bbb", "c":"ccc"}') old
  FULL OUTER JOIN json_each_text('{"a":"aaa", "b":"jjj", "d":"ddd"}') new ON new.key = old.key 
WHERE 
  new.value IS DISTINCT FROM old.value
结果是

{"b" : "bbb", "c" : "ccc", "d" : null}

此方法仅比较json的第一级。它不会遍历整个对象树。

感谢您的回答:创建函数似乎是件好事。但是,<代码>'{b:“bbb”,“c:“ccc”,“d”:null}'不是打字错误,因为
“d”
不在第1行,因此函数应该返回
“d”:null
在我的问题中,我将“json数组”更改为“json对象”;我不知道这些定义显示我们可以在postgres 9.4中使用它。这非常优雅,Dmitry。我用它作为一个简单的json审计/日志触发器——希望没问题。您可以使用
IF object_result='{}::jsonb然后
作为注释,因为
jsonb_build_object
以及使用
|
连接jsonb对象来测试空json对象,这是第9.5+页。我认为您可以使用
json_build_object(…)::jsonb
来解决第一个问题,但我认为在9.5之前没有办法连接两个jsonb字段(没有足够的注释点),因为它工作得很好,但是jsonb_typeof(val1)=“null”检查只适用于“null”字符串/jsonb值。如果将实际null传递给val1,它将返回null。更改
如果val1为NULL或jsonb_typeof(val1)=“NULL”
让我返回整个val2,对于val1为NULL的情况(此场景在执行滞后函数时出现,第一行)哇,这太棒了!