PostgreSQL 比较两个 jsonb 对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36041784/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 02:12:21  来源:igfitidea点击:

PostgreSQL compare two jsonb objects

postgresqljsonbpostgresql-9.5

提问by Joost D?bken

With PostgreSQL(v9.5), the JSONBformats give awesome opportunities. But now I'm stuck with what seems like a relatively simple operation;

使用PostgreSQL(v9.5)JSONB格式提供了极好的机会。但现在我被困在一个看似相对简单的操作上;

compare two jsonb objects; see what is different or missing in one document compared to the other.

比较两个 jsonb 对象;查看一份文件与另一份文件相比有何不同或缺失。

What I have so far

到目前为止我所拥有的

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

Should return the difference of row 1 compared to row 2:

应该返回第 1 行与第 2 行的差异:

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

Instead it returns also duplicates ({"a": "aaa"}). Also; there might be a more elegant approach in general!

相反,它也返回重复项 ( {"a": "aaa"})。还; 一般可能有更优雅的方法!

回答by Dmitry Savinkov

UPDATED

更新

CREATE OR REPLACE FUNCTION jsonb_diff_val(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
  result JSONB;
  v RECORD;
BEGIN
   result = val1;
   FOR v IN SELECT * FROM jsonb_each(val2) LOOP
     IF result @> jsonb_build_object(v.key,v.value)
        THEN result = result - v.key;
     ELSIF result ? v.key THEN CONTINUE;
     ELSE
        result = result || jsonb_build_object(v.key,'null');
     END IF;
   END LOOP;
   RETURN result;
END;
$$ LANGUAGE plpgsql;

Query:

询问:

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)

回答by J. Raczkiewicz

I have created similar function that would scan the object recursively and will return the difference between new object and old object. I was not able to find a 'nicer' way to determine if jsonb object 'is empty' - so would be grateful for any suggestion how to simplify that. I plan to use it to keep track of updates made to the jsonb objects, so I store only what have changed.

我创建了类似的函数,它将递归扫描对象并返回新对象和旧对象之间的差异。我无法找到一种“更好”的方法来确定 jsonb 对象是否“为空”——所以如果你有任何关于如何简化它的建议,我将不胜感激。我打算用它来跟踪对 jsonb 对象所做的更新,所以我只存储发生了变化的内容。

Here is the function:

这是函数:

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;

Then simple query looks like this:

然后简单的查询如下所示:

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)

回答by Sahap Asci

Here is a solution without creating a new function;

这是一个无需创建新函数的解决方案;

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

The result is;

结果是;

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

This method only compares first level of json. It does NOT traverse the whole object tree.

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

回答by langpavel

My solution is not recursive but you can use it for detecting common key/values:

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

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

Result can look like this:

结果可能如下所示:

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}

回答by ThePianoDentist

(not enough points to comment)

(点数不够评论)

for https://stackoverflow.com/a/37278190/3920439, it worked great,

对于https://stackoverflow.com/a/372​​78190/3920439,效果很好,

however the jsonb_typeof(val1) = 'null' check just works for 'null' strings/jsonb values.

但是 jsonb_typeof(val1) = 'null' 检查仅适用于 'null' 字符串/jsonb 值。

if you pass an actual null into val1, it will return null.

如果您将实际空值传递给 val1,它将返回空值。

changing IF val1 IS NULL OR jsonb_typeof(val1) = 'null'let me return whole of val2, for case where val1 was null (this scenario comes up when doing lag functions, for the first row)

更改IF val1 IS NULL OR jsonb_typeof(val1) = 'null'让我返回整个 val2,对于 val1 为空的情况(在执行滞后函数时出现这种情况,对于第一行)