Microsoft SQL 相当于 Oracle JSON_EQUAL 的服务器
Microsoft SQL Server equivalent of Oracle JSON_EQUAL
Oracle 有一个 JSON_EQUAL
函数,在手册中定义为:
The JSON_EQUAL condition was introduced in Oracle 18c to allow JSON documents to be compared regardless of member order or document formatting.
Microsoft SQL-server 中似乎没有等效项。我错了吗?如果没有,有人知道(基于 CLR 的?)替代方案的特别好的实现吗?
或者我是否需要自己使用 CLR 构建一个类似的函数?
需要说明的是,此问题适用于所有 SQL 版本以上且包括 2016 的 JSON 支持。如果要求功能仅在 2019 年,那将是一个完全可以接受的答案。
老实说,如果我必须这样做,我可能只会使用 SQLCLR,它可能更高效且更容易编码。
您至少可以很容易地比较根对象的变量:
SELECT CASE WHEN json.j1 IS NULL AND json.j2 IS NULL
OR json.j1 IS NOT NULL AND json.j2 IS NOT NULL
AND NOT EXISTS (SELECT 1
FROM OPENJSON(json.j1) j1
FULL JOIN OPENJSON(json.j2) j2 ON j2.[key] = j1.[key]
WHERE j1.[key] IS NULL OR j2.[key] IS NULL
OR j1.value <> j2.value OR j1.type <> j2.type
) THEN 'Same' ELSE 'Different' END,
j1, j2
FROM json;
要进行全面的深度比较,您需要使用递归 CTE, 这证明很难编写代码,而且速度很慢。
但是给你。我已经尝试向 fiddle 添加一堆测试用例,我希望进一步测试
You can't use outer joins in rCTE's, but you can use APPLY
, which is why we need two functions
-- This function compares just single values,
-- returns only non-matching scalars, or objects or arrays
CREATE FUNCTION JSON_COMPARE(@j1 nvarchar(max), @j2 nvarchar(max))
RETURNS TABLE AS RETURN
SELECT
ObjectOrArray = CASE WHEN j1.[type] = j2.[type] AND j1.[type] IN (4,5)
THEN 1 ELSE 0 END,
v1 = CASE WHEN j1.[type] = j2.[type] AND j1.[type] IN (4,5) THEN j1.value END,
v2 = CASE WHEN j1.[type] = j2.[type] AND j2.[type] IN (4,5) THEN j2.value END
FROM OPENJSON(@j1) j1
FULL JOIN OPENJSON(@j2) j2 ON j2.[key] = j1.[key] -- join by key, or array index
WHERE j1.[key] IS NULL -- will only be if there is a missing key
OR j2.[key] IS NULL -- as above
OR j1.[type] <> j2.[type] -- or different types
OR j1.[type] IN (4,5) -- we also want all objects and arrays
OR j1.value <> j2.value; -- or they're non-matching scalars
-- We use an rCTE, applying JSON_COMPARE to each level of the Json,
-- and look for only non-matches on the outside
CREATE FUNCTION JSON_EQUAL(@j1 nvarchar(max), @j2 nvarchar(max))
RETURNS TABLE AS RETURN
WITH cte AS (
SELECT
j.ObjectOrArray,
j.v1,
j.v2
FROM JSON_COMPARE('[' + @j1 + ']', '[' + @j2 + ']') j
-- necessary to check root objects' types
UNION ALL
SELECT
j.ObjectOrArray,
j.v1,
j.v2
FROM cte
CROSS APPLY JSON_COMPARE(cte.v1, cte.v2) j
WHERE cte.ObjectOrArray = 1 -- we only need to descend for objects or arrays
)
SELECT IsMatch =
CASE WHEN (@j1 IS NULL AND @j2 IS NULL) -- both roots are null
OR (@j1 IS NOT NULL AND @j2 IS NOT NULL AND -- both non null and...
NOT EXISTS (SELECT 1
FROM cte
WHERE ObjectOrArray = 0 -- we only want scalars that don't match
)) THEN 1 ELSE 0 END;
SELECT j.IsMatch, j1, j2
FROM json
CROSS APPLY JSON_EQUAL(j1, j2) j;
Oracle 有一个 JSON_EQUAL
函数,在手册中定义为:
The JSON_EQUAL condition was introduced in Oracle 18c to allow JSON documents to be compared regardless of member order or document formatting.
Microsoft SQL-server 中似乎没有等效项。我错了吗?如果没有,有人知道(基于 CLR 的?)替代方案的特别好的实现吗?
或者我是否需要自己使用 CLR 构建一个类似的函数?
需要说明的是,此问题适用于所有 SQL 版本以上且包括 2016 的 JSON 支持。如果要求功能仅在 2019 年,那将是一个完全可以接受的答案。
老实说,如果我必须这样做,我可能只会使用 SQLCLR,它可能更高效且更容易编码。
您至少可以很容易地比较根对象的变量:
SELECT CASE WHEN json.j1 IS NULL AND json.j2 IS NULL
OR json.j1 IS NOT NULL AND json.j2 IS NOT NULL
AND NOT EXISTS (SELECT 1
FROM OPENJSON(json.j1) j1
FULL JOIN OPENJSON(json.j2) j2 ON j2.[key] = j1.[key]
WHERE j1.[key] IS NULL OR j2.[key] IS NULL
OR j1.value <> j2.value OR j1.type <> j2.type
) THEN 'Same' ELSE 'Different' END,
j1, j2
FROM json;
要进行全面的深度比较,您需要使用递归 CTE, 这证明很难编写代码,而且速度很慢。
但是给你。我已经尝试向 fiddle 添加一堆测试用例,我希望进一步测试
You can't use outer joins in rCTE's, but you can use
APPLY
, which is why we need two functions
-- This function compares just single values,
-- returns only non-matching scalars, or objects or arrays
CREATE FUNCTION JSON_COMPARE(@j1 nvarchar(max), @j2 nvarchar(max))
RETURNS TABLE AS RETURN
SELECT
ObjectOrArray = CASE WHEN j1.[type] = j2.[type] AND j1.[type] IN (4,5)
THEN 1 ELSE 0 END,
v1 = CASE WHEN j1.[type] = j2.[type] AND j1.[type] IN (4,5) THEN j1.value END,
v2 = CASE WHEN j1.[type] = j2.[type] AND j2.[type] IN (4,5) THEN j2.value END
FROM OPENJSON(@j1) j1
FULL JOIN OPENJSON(@j2) j2 ON j2.[key] = j1.[key] -- join by key, or array index
WHERE j1.[key] IS NULL -- will only be if there is a missing key
OR j2.[key] IS NULL -- as above
OR j1.[type] <> j2.[type] -- or different types
OR j1.[type] IN (4,5) -- we also want all objects and arrays
OR j1.value <> j2.value; -- or they're non-matching scalars
-- We use an rCTE, applying JSON_COMPARE to each level of the Json,
-- and look for only non-matches on the outside
CREATE FUNCTION JSON_EQUAL(@j1 nvarchar(max), @j2 nvarchar(max))
RETURNS TABLE AS RETURN
WITH cte AS (
SELECT
j.ObjectOrArray,
j.v1,
j.v2
FROM JSON_COMPARE('[' + @j1 + ']', '[' + @j2 + ']') j
-- necessary to check root objects' types
UNION ALL
SELECT
j.ObjectOrArray,
j.v1,
j.v2
FROM cte
CROSS APPLY JSON_COMPARE(cte.v1, cte.v2) j
WHERE cte.ObjectOrArray = 1 -- we only need to descend for objects or arrays
)
SELECT IsMatch =
CASE WHEN (@j1 IS NULL AND @j2 IS NULL) -- both roots are null
OR (@j1 IS NOT NULL AND @j2 IS NOT NULL AND -- both non null and...
NOT EXISTS (SELECT 1
FROM cte
WHERE ObjectOrArray = 0 -- we only want scalars that don't match
)) THEN 1 ELSE 0 END;
SELECT j.IsMatch, j1, j2
FROM json
CROSS APPLY JSON_EQUAL(j1, j2) j;