众所周知,目前 PostgreSQL 没有比较两个 json 值的方法。类似的比较json = json不起作用。但是投射json到text之前呢?
然后
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 兼容
您也可以使用@>运算符。假设你有 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
是的,您的方法存在多个问题(即转换为文本)。考虑下面的例子
select ('{"x":"a", "y":"b"}')::json::text = ('{"y":"b", "x":"a"}')::json::text;
Run Code Online (Sandbox Code Playgroud)
这就像您的第一个示例示例,除了我翻转了第二个对象的x和y键的顺序,现在它返回 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 对象中的相应键进行匹配。然后我们只保留对应于以下两种情况之一的行:
这些是唯一“见证”两个对象不等式的情况,因此我们用 a 包裹所有东西NOT exists(...),即如果我们没有找到任何不等式的见证,则对象是相等的。
如果需要支持其他类型的 JSON 值(例如数组、嵌套对象等),可以plpgsql根据上述思路编写函数。
最值得注意的是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)
| 归档时间: |
|
| 查看次数: |
16431 次 |
| 最近记录: |