【问题标题】:Concatenate or merge two json objects in SQL Server在 SQL Server 中连接或合并两个 json 对象
【发布时间】:2018-02-21 17:02:49
【问题描述】:

我有一个表格,在一列中存储 json。我想通过合并另一个 json 来更新 json 值。

类似:

insert into mytable 
values ('{ "a": "b" ')

update mytable 
set jsonColumn = JSON_MERGE(jsonColumn, '{ "c": 2 }')

这应该导致像这样的 json:

{ "a": "b", "c": 2 }

不幸的是,没有这样的JSON_MERGE 函数,JSON_MODIFY 让我只能一一修改列。我有太多的,包括嵌套属性。

我基本上是在寻找 postgres || concatenation operator 的等价物。

【问题讨论】:

  • Jan,不是您要寻找的答案,但您可以创建一个名为 JSON_MERGE 的用户函数来执行此操作。这些函数将接受列中的现有值,以及要添加的值的 varchar(MAX)。您可以查找“{”和“}”并使用内置的 SQL REPLACE 函数。但是,我可以看到更复杂的 JSON 在哪里会变得更复杂。 JSON_MODIFY 函数不能应用于 UPDATE 语句中的集合吗?
  • 在处理复杂的嵌套结构时,字符串操作可能会变得过于混乱。我无法让 JSON_MODIFY 在更新中工作。

标签: json sql-server sql-server-json


【解决方案1】:

在 Sql Server 2016 中,无法在 JSON_MODIFY 中使用变量作为 json 路径,因此我不确定是否有针对此问题的优雅解决方案。

如果你有 Sql Server 2017,那么它似乎是可能的。

create function dbo.fn_json_merge
(
    @a nvarchar(max),
    @b nvarchar(max)
)
returns nvarchar(max)
as
begin
    if left(@a, 1) = '{' and left(@b, 1) = '{' begin
        select
            @a = case when d.[type] in (4,5) then json_modify(@a, concat('$.',d.[key]), json_query(d.[value])) else @a end,
            @a = case when d.[type] not in (4,5) then json_modify(@a, concat('$.',d.[key]), d.[value]) else @a end
        from openjson(@b) as d;
    end else if left(@a, 1) = '[' and left(@b, 1) = '{' begin
        select @a = json_modify(@a, 'append $', json_query(@b));
    end else begin
        select @a = concat('[', @a, ',', right(@b, len(@b) - 1));
    end;

    return @a;
end;

几个笔记:

  • 为简单起见,我没有添加检查两个对象是否实际上是有效的 json;
  • 不知道有没有更好的方法来检查给定的字符串是json数组还是json对象;
  • 无法使用 json_modify 添加数组的第一个元素,因此可以回退到简单的 CONCAT,以防第一个字符串是对象,第二个是数组;
  • 我必须创造性地使用JSON_QUERY 函数,以便正确插入json;
  • 我使用的事实是,如果您在SELECT 语句中分配变量,那么您可以在分配语句中使用该变量的先前值;

sql server fiddle demo

postgresql fiddle example

更新我添加了一些改进的版本,应该可以更好地处理不同类型的值:

create function dbo.fn_json_merge
(
    @a nvarchar(max),
    @b nvarchar(max)
)
returns nvarchar(max)
as
begin
    if left(@a, 1) = '{' and left(@b, 1) = '{' begin
        select @a =
            case
                when d.[type] in (4,5) then
                    json_modify(@a, concat('$.',d.[key]), json_query(d.[value]))
                when d.[type] in (3) then
                    json_modify(@a, concat('$.',d.[key]), cast(d.[value] as bit))
                when d.[type] in (2) and try_cast(d.[value] as int) = 1 then
                    json_modify(@a, concat('$.',d.[key]), cast(d.[value] as int))
                when d.[type] in (0) then
                    json_modify(json_modify(@a, concat('lax $.',d.[key]), 'null'), concat('strict $.',d.[key]), null)
                else
                    json_modify(@a, concat('$.',d.[key]), d.[value])
            end
        from openjson(@b) as d
    end else if left(@a, 1) = '[' and left(@b, 1) = '{' begin
        select @a = json_modify(@a, 'append $', json_query(@b))
    end else begin
        select @a = concat('[', @a, ',', right(@b, len(@b) - 1))
    end

    return @a
end

sql fiddle demo

【讨论】:

  • 只是一个小改进:您可以引用键concat('$."',d.[key],'"'),这样恰好也是数字的键字符串也可以工作(例如{ "0": "abc" })。当数值不是整数时还有一个问题:这里会变成字符串。我认为一个好的解决方法是在类型为 2 时根据字符串检测到正确的精度(例如1.23 转换为decimal(3,2))转换为小数。(不确定他们为什么调用从@987654338 获得的类型@"int",当它可以是 JSON 中的十进制数时。SQL Server 中的 JSON 支持还需要一些改进。)
【解决方案2】:

您可以执行类似于该代码的操作:

DECLARE @json1 nvarchar(max),
        @json2 nvarchar(max)

DECLARE @result AS nvarchar(max)

SET @json1 = N'{"a": "1", "c": "3"}'

SET @json2 = N'{"b": "2"}'

SELECT
  @result = COALESCE(@result + ', ', '') + '"' + [key] + '":"' + value + '"'
FROM (SELECT
  [key],
  value
FROM OPENJSON(@json1)
UNION ALL
SELECT
  [key],
  value
FROM OPENJSON(@json2)) AS x

SET @result = '{' + @result + '}'

PRINT @result

@结果是

{"a":"1", "c":"3", "b":"2"}

【讨论】:

  • 谢谢。不幸的是,这不适用于嵌套结构。像 jsonA {"a": { "a": "a" } } 和 jsonB {"a": { "b": "b" } } 我也希望有更优雅的解决方案。
  • 在这种情况下你期望输出什么?
  • js Object.assign 会做的一样。所以{"a": { "b": "b"} }
【解决方案3】:

聚会也有点晚了,但我们在尝试在 MS SQL 中合并 JSON 时遇到了类似的问题。我们还希望它是递归的,并允许我们为“union”、“concat”和“replace”等数组定义策略。

我们的 JSON 操作解决方案(如 merge、JSON path expressions 等)刚刚开源,现在可以在 @Github 使用

请随意使用、评论和贡献,以便我们进一步改进 MS SQL 的 JSON 方法。

【讨论】:

  • 谢谢!这组函数非常棒,它们也有空值处理。
【解决方案4】:

JSON_MODIFY 中有附加的概念,但这取决于标签名称。请看下面的例子。如果你有标签名称,那么它可以工作,否则不能。阅读更多来自https://docs.microsoft.com/en-us/sql/t-sql/functions/json-modify-transact-sql。另请注意,您可以在某个时间点附加一个值

PRINT 'EXAMPLE 1

'

DECLARE @j NVARCHAR(MAX)
SET @j = '{"k" : ["a","b"] }'
PRINT @J
SET @J=JSON_MODIFY(JSON_MODIFY(@j,'append $.k','c'),'append $.k','2')
PRINT @J
GO

PRINT '
EXAMPLE 2

'

DECLARE @j NVARCHAR(MAX)
SET @j = '{"a":"b"}'
PRINT @J
SET @J=JSON_MODIFY(@J,'append $','c:2')
PRINT @J
GO

输出

EXAMPLE 1

{"k" : ["a","b"] }
{"k" : ["a","b","c","2"] }

EXAMPLE 2

{"a":"b"}
{"a":"b"}

【讨论】:

  • 正如我所见,追加仅适用于数组。我需要合并复杂的 json 结构。
  • 哦..那么你需要做一些自定义的事情。
【解决方案5】:

我参加聚会有点晚了,但我现在遇到了类似的事情。我根据this 问题提出了一个解决方案,它将合并顶级 JSON 项。

这样做的一些例子:

{"a":1} + {"B":2} = {"a":1,"B":2}
{"x":true,"y":{"a":"b","c":"d"}} + {"y":{"a":"z"}} = {"x":true,"y":{"a":"z"}}

此版本不会向下钻取以合并子项(例如,在我的第二个示例中,它不会保留 ["y"]["c"] 索引)。我认为这样做可以对其进行增强,但这是一个快速的概念验证版本,出于我的目的,我不需要担心这些更新。

内容:

--- Merge the top-level items of two JSON object strings into one JSON
--- based off of: https://stackoverflow.com/questions/47489030/generate-a-json-string-containing-the-differences-in-two-other-json-strings-usin

 DECLARE  @jsonA     NVARCHAR(MAX) = '{"CommonValue":"OriginalThing", "OldValue": "A", "ComplexValue": {"InnerValue": "ABC"}}'
        ,@jsonB     NVARCHAR(MAX) = '{"CommonValue":"ChangedThing", "NewValue": "B", "Number": 22}'
        ,@result    NVARCHAR(MAX) = ''

--- Catalog of differences.
DECLARE @JsonDiff TABLE 
(
    OldKey CHAR(128),
    OldValue NVARCHAR(MAX),
    OldType CHAR(1),
    NewKey CHAR(128),
    NewValue NVARCHAR(MAX),
    NewType CHAR(1)
)
--- Temporary table for output rows.
--- The table could probably clipped out for production stuff.
--- For proof-of-concept, it's useful for querying results
---  before building the JSON string.
DECLARE @JsonData TABLE 
(
    NewKey CHAR(128),
    NewValue NVARCHAR(MAX),
    NewType CHAR(1)
)

;WITH DSA AS
(
    SELECT *
    FROM OPENJSON(@jsonA)   
)
,DSB AS
(
    SELECT *
    FROM OPENJSON(@jsonB)   
)
INSERT INTO @JsonDiff (OldKey, OldValue, OldType, NewKey, NewValue, NewType)
SELECT a.[Key] aKey, a.[Value] aValue, a.[Type] aType, b.[Key] bKey, b.[Value] bValue, b.[Type] bType
FROM DSA A
FULL OUTER JOIN DSB B ON A.[key] = B.[key]

INSERT INTO @JsonData (NewKey, NewValue, NewType) 
    SELECT OldKey as k, OldValue as v, OldType as t
    FROM @JsonDiff
    WHERE OldKey IS NOT NULL AND NewKey IS NULL
    UNION
    SELECT NewKey as k, NewValue as v, NewType as t
    FROM @JsonDiff
    WHERE NewKey IS NOT NULL

--- a few queries for display purposes
--- select * FROM @JsonDiff
select NewKey, NewValue FROM @JsonData

SELECT @result += CONCAT ( '"', TRIM([NewKey]), '":'
    ,IIF([NewType] = 1, CONCAT('"', [NewValue], '"'), [NewValue]) -- If the item is a string, then add quotes.
    ,','
)
FROM @JsonData

--- Print the JSON
SELECT CONCAT('{', LEFT(@result, LEN(@result) - 1), '}')

编辑:这是最后一点的简化版本,不需要@JsonData:

SELECT @result += CONCAT ( '"', TRIM([k]), '":'
    ,IIF([t] = 1, CONCAT('"', [v], '"'), [v]) -- If the item is a string, then add quotes.
    ,','
)
FROM 
    (
        SELECT OldKey as k, OldValue as v, OldType as t
            FROM @JsonDiff
            WHERE OldKey IS NOT NULL AND NewKey IS NULL
        UNION
        SELECT NewKey as k, NewValue as v, NewType as t
            FROM @JsonDiff
            WHERE NewKey IS NOT NULL
    ) as mid

--- Print the JSON
SELECT CONCAT('{', LEFT(@result, LEN(@result) - 1), '}')

【讨论】:

    【解决方案6】:

    还可以看看:

    SELECT (
        SELECT 
        (
            SELECT    ID AS "test.id" 
            FROM      [Table1] 
            FOR       JSON AUTO
        )             AS 'test1', 
                      'id' AS 'test2'
        FROM          test2
        FOR           JSON AUTO
    )                 AS JSON
    

    【讨论】:

      【解决方案7】:

      基于@JorgeRibeiro 的回答,如果您想编辑现有值,这也仅适用于一级 json-

      DECLARE @json1 nvarchar(max),
              @json2 nvarchar(max)
      
      DECLARE @result AS nvarchar(max)
      
      SET @json1 = N'{"a": "1", "c": "3"}'
      
      SET @json2 = N'{"a": "2","b" : "4"}'
      
      SELECT
        @result = COALESCE(@result + ', ', '') + '"' + [key] + '":"' + value + '"'
      FROM (SELECT
        [key],
        value
      FROM OPENJSON(@json1) where [key] not in (Select Distinct [key] from OPENJSON(@json2))
      UNION ALL
      SELECT
        [key],
        value
      FROM OPENJSON(@json2)) AS x
      
      SET @result = '{' + @result + '}'
      
      PRINT @result
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-23
        • 1970-01-01
        • 2021-04-16
        相关资源
        最近更新 更多