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;

db<>fiddle.uk


要进行全面的深度比较,您需要使用递归 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;

db<>fiddle