PostgreSQL:比较 jsons

Map*_*ser 6 postgresql json

众所周知,目前 PostgreSQL 没有比较两个 json 值的方法。类似的比较json = json不起作用。但是投射jsontext之前呢?

然后

select ('{"x":"a", "y":"b"}')::json::text = 
('{"x":"a", "y":"b"}')::json::text
Run Code Online (Sandbox Code Playgroud)

返回 true

尽管

select ('{"x":"a", "y":"b"}')::json::text = 
('{"x":"a", "y":"d"}')::json::text
Run Code Online (Sandbox Code Playgroud)

返回 false

我尝试了几种具有更复杂对象的变体,它按预期工作。

这个解决方案有什么问题吗?

更新:

需要与 v9.3 兼容

Abe*_*rio 8

您也可以使用@>运算符。假设你有 A 和 B,都是 JSONB 对象,所以A = B如果:

A @> B AND A <@ B
Run Code Online (Sandbox Code Playgroud)

在此处阅读更多信息:https : //www.postgresql.org/docs/current/functions-json.html

  • 这只适用于简单对象,但不适用于直接包含数组或 jsonb 数组的对象。请参阅我的回答以获得全面的解决方案。 (2认同)

red*_*neb 7

是的,您的方法存在多个问题(即转换为文本)。考虑下面的例子

select ('{"x":"a", "y":"b"}')::json::text = ('{"y":"b", "x":"a"}')::json::text;
Run Code Online (Sandbox Code Playgroud)

这就像您的第一个示例示例,除了我翻转了第二个对象的xy键的顺序,现在它返回 false,即使对象是相等的。

另一个问题是json保留空白,所以

select ('{"x":"a", "y":"b"}')::json::text = ('{ "x":"a", "y":"b"}')::json::text;
Run Code Online (Sandbox Code Playgroud)

返回 false 只是因为我x在第二个对象之前添加了一个空格。

一个适用于 v9.3 的解决方案是使用该json_each_text函数将两个 JSON 对象展开成表,然后比较这两个表,例如像这样:

SELECT NOT exists(
    SELECT
    FROM json_each_text(('{"x":"a", "y":"b"}')::json) t1
         FULL OUTER JOIN json_each_text(('{"y":"b", "x":"a"}')::json) t2 USING (key)
    WHERE t1.value<>t2.value OR t1.key IS NULL OR t2.key IS NULL
)
Run Code Online (Sandbox Code Playgroud)

请注意,这仅适用于两个 JSON 值是对象,其中每个键的值是字符串。

键位于 : 内的查询中exists:在该查询中,我们将第一个 JSON 对象中的所有键与第二个 JSON 对象中的相应键进行匹配。然后我们只保留对应于以下两种情况之一的行:

  • 两个 JSON 对象中都存在一个键,但对应的值不同
  • 一个键只存在于两个 JSON 对象之一中,而不存在于另一个

这些是唯一“见证”两个对象不等式的情况,因此我们用 a 包裹所有东西NOT exists(...),即如果我们没有找到任何不等式的见证,则对象是相等的。

如果需要支持其他类型的 JSON 值(例如数组、嵌套对象等),可以plpgsql根据上述思路编写函数。


Ove*_*ryd 6

最值得注意的是A @> B AND B @> A,将表明TRUE它们是否都是相同的 JSONB 对象。

但是,在假设它适用于所有类型的 JSONB 值时要小心,如以下查询所示:

select
    old,
    new,
    NOT(old @> new AND new @> old) as changed
from (
    values
        (
        '{"a":"1", "b":"2", "c": {"d": 3}}'::jsonb,
        '{"b":"2", "a":"1", "c": {"d": 3, "e": 4}}'::jsonb
        ),
        (
        '{"a":"1", "b":"2", "c": {"d": 3, "e": 4}}'::jsonb,
        '{"b":"2", "a":"1", "c": {"d": 3}}'::jsonb
        ),
        (
        '[1, 2, 3]'::jsonb,
        '[3, 2, 1]'::jsonb
        ),
        (
        '{"a": 1, "b": 2}'::jsonb,
        '{"b":2, "a":1}'::jsonb
        ),
        (
        '{"a":[1, 2, 3]}'::jsonb,
        '{"b":[3, 2, 1]}'::jsonb
        )
) as t (old, new)
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是 JSONB 数组没有正确比较,就像在 JSON 中一样,[1, 2, 3] != [3, 2, 1]但 PostgresTRUE仍然返回。

正确的解决方案将递归地迭代 json 的内容并以不同的方式比较数组和对象。我很快就构建了一组可以实现这一目标的函数。

像这样使用它们SELECT jsonb_eql('[1, 2, 3]'::jsonb, '[3, 2, 1]'::jsonb)(结果是FALSE)。

CREATE OR REPLACE FUNCTION jsonb_eql (a JSONB, b JSONB) RETURNS BOOLEAN AS $$
DECLARE
BEGIN
  IF (jsonb_typeof(a) != jsonb_typeof(b)) THEN
    RETURN FALSE;
  ELSE
    IF (jsonb_typeof(a) = 'object') THEN
      RETURN jsonb_object_eql(a, b);
    ELSIF (jsonb_typeof(a) = 'array') THEN
      RETURN jsonb_array_eql(a, b);
    ELSIF (COALESCE(jsonb_typeof(a), 'null') = 'null') THEN
      RETURN COALESCE(a, 'null'::jsonb) = 'null'::jsonb AND COALESCE(b, 'null'::jsonb) = 'null'::jsonb;
    ELSE
      RETURN coalesce(a = b, FALSE);
    END IF;
  END IF;
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
CREATE OR REPLACE FUNCTION jsonb_object_eql (a JSONB, b JSONB) RETURNS BOOLEAN AS $$
DECLARE
   _key_a text;
   _val_a jsonb;
   _key_b text;
   _val_b jsonb;
BEGIN
  IF (jsonb_typeof(a) != jsonb_typeof(b)) THEN
    RETURN FALSE;
  ELSIF (jsonb_typeof(a) != 'object') THEN
    RETURN jsonb_eql(a, b);
  ELSE
    FOR _key_a, _val_a, _key_b, _val_b IN
      SELECT t1.key, t1.value, t2.key, t2.value FROM jsonb_each(a) t1
      LEFT OUTER JOIN (
        SELECT * FROM jsonb_each(b)
      ) t2 ON (t1.key = t2.key)
    LOOP
      IF (_key_a != _key_b) THEN
        RETURN FALSE;
      ELSE
        RETURN jsonb_eql(_val_a, _val_b);
      END IF;
    END LOOP;
    RETURN a = b;
  END IF;
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
CREATE OR REPLACE FUNCTION jsonb_array_eql (a JSONB, b JSONB) RETURNS BOOLEAN AS $$
DECLARE
   _val_a jsonb;
   _val_b jsonb;
BEGIN
  IF (jsonb_typeof(a) != jsonb_typeof(b)) THEN
    RETURN FALSE;
  ELSIF (jsonb_typeof(a) != 'array') THEN
    RETURN jsonb_eql(a, b);
  ELSE
    FOR _val_a, _val_b IN
      SELECT jsonb_array_elements(a), jsonb_array_elements(b)
    LOOP
      IF (NOT(jsonb_eql(_val_a, _val_b))) THEN
        RETURN FALSE;
      END IF;
    END LOOP;
    RETURN TRUE;
  END IF;
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)