众所周知,目前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的兼容性
答案 0 :(得分:1)
是的,您的方法存在多个问题(即转换为文本)。请考虑以下示例
select ('{"x":"a", "y":"b"}')::json::text = ('{"y":"b", "x":"a"}')::json::text;
这就像你的第一个示例示例,除了我翻转了第二个对象的x
和y
键的顺序,现在它返回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对象中的相应键匹配。然后我们只保留与以下两种情况之一相对应的行:
这是唯一能够“见证”两个对象不平等的情况,因此我们用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;