PostgreSQL:比较jsons

时间:2018-03-20 17:59:47

标签: json postgresql

众所周知,目前PostgreSQL没有比较两个json值的方法。像json = json这样的比较并不起作用。但是之前如何将json投射到text

然后

select ('{"x":"a", "y":"b"}')::json::text = 
('{"x":"a", "y":"b"}')::json::text

返回true

,而

select ('{"x":"a", "y":"b"}')::json::text = 
('{"x":"a", "y":"d"}')::json::text

返回false

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

此解决方案中是否有任何问题?

更新

需要与v9.3的兼容性

3 个答案:

答案 0 :(得分:1)

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

select ('{"x":"a", "y":"b"}')::json::text = ('{"y":"b", "x":"a"}')::json::text;

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

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

select ('{"x":"a", "y":"b"}')::json::text = ('{ "x":"a", "y":"b"}')::json::text;

返回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
)

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

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

  • 两个JSON对象中都存在一个键,但相应的值不同
  • 密钥仅存在于两个JSON对象之一而不存在于其他

这是唯一能够“见证”两个对象不平等的情况,因此我们用NOT exists(...)包装所有内容,即如果我们没有找到任何不等式的证据,则对象是相等的。

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

答案 1 :(得分:0)

您还可以使用@>运算符。假设您有两个JSONB对象A和B,因此,如果A = B

A @> B AND A <@ B

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

答案 2 :(得分:0)

如果mounted()都等于JSONB对象,则A @> B AND B @> A最为明显。

但是,假设它适用于各种JSONB值时要小心,如以下查询所示:

TRUE

此方法存在的问题是,JSONB数组未正确比较,例如JSON 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) ,但是Postgres返回[1, 2, 3] != [3, 2, 1]

正确的解决方案将递归地遍历json的内容,并以不同的方式比较数组和对象。我已经快速构建了一组可以实现此目的的功能。

TRUE一样使用它们(结果为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;
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;