PostgreSQL比较两个jsonb对象

Posted

技术标签:

【中文标题】PostgreSQL比较两个jsonb对象【英文标题】:PostgreSQL compare two jsonb objects 【发布时间】:2016-07-02 16:38:26 【问题描述】:

对于PostgreSQL(v9.5),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")。还;一般来说,可能会有更优雅的方法!

【问题讨论】:

(没有足够的评论点)对于***.com/a/37278190/3920439,它工作得很好,但是 jsonb_typeof(val1) = 'null' 检查只适用于 'null' 字符串/jsonb 值。如果将实际的 null 传递给 val1,它将返回 null。更改 IF val1 IS NULL OR jsonb_typeof(val1) = 'null' 让我返回整个 val2,以防 val1 为空(这种情况在执行滞后函数时出现,对于第一行) 【参考方案1】:

更新

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;

查询:

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)

【讨论】:

感谢您的回答:创建函数似乎是一件好事。然而; '"b":"bbb", "c":"ccc", "d":null' 不是拼写错误,因为 "d" 不在第 1 行,因此该函数应返回 "d":null 我在我的问题中将“json array”更改为“json objects”;我不知道这些定义 我们如何在 postgres 9.4 中使用它 这很优雅,德米特里。我已将其用于github.com/rorycl/pg_json_logger 的简单 json 审计/日志触发器——希望没问题。【参考方案2】:

我创建了类似的函数,它会递归地扫描对象并返回新对象和旧对象之间的差异。我无法找到一种“更好”的方法来确定 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)

【讨论】:

你可以用IF object_result = ''::jsonb THEN测试空的json对象 作为注释,因为jsonb_build_object|| 用于连接jsonb对象,这是PG 9.5+。我认为您可以使用json_build_object(...)::jsonb 来解决第一个问题,但我认为在 9.5 之前没有办法连接两个 jsonb 字段。【参考方案3】:

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

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。它不会遍历整个对象树。

【讨论】:

【参考方案4】:

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

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

【讨论】:

哇,那个太棒了!

以上是关于PostgreSQL比较两个jsonb对象的主要内容,如果未能解决你的问题,请参考以下文章

用于比较 JSONB 值的 PostgreSQL 索引

使用 Sequelize.js 和 PostgreSQL 在关联模型上查询 JSONB 字段

Postgresql:搜索jsonb对象数组时如何使用动态值?

PostgreSQL 查询 JSONB 字段中的对象数组

PostgreSQL - 向 JSONB 数组的每个对象添加键

在 postgresql、选项数组或对象中插入 jsonb 数据,有效方式