In Sql Server 2016 it's not possible to use variables as json path in JSON_MODIFY, so I'm not sure if there's an elegant solution for this problem.
If you have Sql Server 2017, then it seems to be possible.
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;
Couple of notes:
- For the sake of simplicity I didn't add checking that both objects are actually valid json;
- I don't know if there's a better way to check that given string is json array or json object;
- It's not possible to add first element of array with json_modify so there's a fallback to simple
CONCATin case first string is an object and second is an array; - I had to creatively use
JSON_QUERYfunction so jsons will be inserted correctly; - I've used the fact that if you assign the variable in
SELECTstatement then you can use previous value of the variable in the assignment statement;
sql server fiddle demo
postgresql fiddle example
update I've added a bit improved version which should work with different types of values better:
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
Answer from roman on Stack OverflowIn Sql Server 2016 it's not possible to use variables as json path in JSON_MODIFY, so I'm not sure if there's an elegant solution for this problem.
If you have Sql Server 2017, then it seems to be possible.
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;
Couple of notes:
- For the sake of simplicity I didn't add checking that both objects are actually valid json;
- I don't know if there's a better way to check that given string is json array or json object;
- It's not possible to add first element of array with json_modify so there's a fallback to simple
CONCATin case first string is an object and second is an array; - I had to creatively use
JSON_QUERYfunction so jsons will be inserted correctly; - I've used the fact that if you assign the variable in
SELECTstatement then you can use previous value of the variable in the assignment statement;
sql server fiddle demo
postgresql fiddle example
update I've added a bit improved version which should work with different types of values better:
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
I have a solution for your issue. I found it while trying to merge 2 different JSON objects and I used JSON_MODIFY and OPENJSON functions.
Sample data:
JSON1: {"a": 1, "b": 2, "c": 3}
JSON2: {"d": 4, "e": 5}
Here the solution
DECLARE @vJSON NVARCHAR(MAX) = N'{"a":1, "b":2, "c":3}'
DECLARE @vJSON2 NVARCHAR(MAX) = N'{"d":4, "e":5}'
SELECT
@vJSON = JSON_MODIFY(@vJSON, CONCAT(N'$.', [Key]), value)
FROM
OPENJSON(@vJSON2)
SELECT @vJSON
--Output: {"a":1, "b":2, "c":3, "d":"4", "e":"5"}
Also it's not a type-safe solution, you can add a case statement to casting values according to type of OPENJSON
SQL Server2016: Merge Operation with json source โ SQLServerCentral Forums
How to merge two json arrays by property id (SQL Server) - Stack Overflow
Merge 2 JSON strings in SQL Server - Stack Overflow
sql server - Combine JSON objects from multiple rows into a single object - Stack Overflow
Your problem can be solved by
SELECT dbo.udf_native_json_merge(@json1,@json2,null)
We faced similar issues trying to merge JSONs in MS SQL. We also wanted it to be recursive and allow us to define a strategy for arrays like "union", "concat" and "replace".
Our solution for JSON manipulations like merge, JSON path expressions and more is open source and available @ Github
Feel free to use, comment and contribute so we can further improve JSON methods for MS SQL.
It's Cumbersome but possible to achieve with SQL Server's built it JSON support.
First, set proper sample data (Please save us this step in your future questions):
DECLARE @Json1 nvarchar(max) =
'{
"id": 1,
"Items": [
{
"id": 1,
"name" : "Item #1"
},
{
"id": 2,
"name" : "Item #2"
}
]
}',
@Json2 nvarchar(max) =
'{
"Items": [
{
"id": 3,
"name": "Item #3"
},
{
"id": 4,
"name": "Item #4"
}
]
}';
Then, wrap a union all query containing openjson and json_query for each one of the variables with a common table expression:
With cteArray as
(
SELECT *
FROM OPENJSON(JSON_QUERY(@Json1, '$.Items'))
WITH(
Id int '$.id',
Name varchar(100) '$.name'
)
UNION ALL
SELECT *
FROM OPENJSON(JSON_QUERY(@Json2, '$.Items'))
WITH(
Id int '$.id',
Name varchar(100) '$.name'
)
)
The result of that union all query is this:
Id Name
1 Item #1
2 Item #2
3 Item #3
4 Item #4
Then, select the id from the first json using json_value, and add a subquery to select everything from the cte with for json path. Add another for json path and specify without_array_wrapper to the outer query:
SELECT JSON_VALUE(@Json1, '$.id') As id,
(
SELECT *
FROM cteArray
FOR JSON PATH
) as Items
FOR JSON PATH,
WITHOUT_ARRAY_WRAPPER
The final result:
{
"id": "1",
"Items": [{
"Id": 1,
"Name": "Item #1"
}, {
"Id": 2,
"Name": "Item #2"
}, {
"Id": 3,
"Name": "Item #3"
}, {
"Id": 4,
"Name": "Item #4"
}
]
}
You can see a live demo on Db<>Fiddle
FOR JSON will treat a string as a string even if it represents valid JSON. You need to use JSON_QUERY to convert a JSON string to an actual JSON object. MIN is required to combine multiple rows to one:
SELECT Id, (
SELECT JSON_QUERY(MIN(CASE WHEN typee = 'APV' THEN jsonStr END)) AS [APV]
, JSON_QUERY(MIN(CASE WHEN typee = 'VN' THEN jsonStr END)) AS [VN]
, JSON_QUERY(MIN(CASE WHEN typee = 'VO' THEN jsonStr END)) AS [VO]
FROM t AS x
WHERE x.id = t.id
FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER
)
FROM t
GROUP BY Id
Demo on db<>fiddle
The problem is that you are storing JSON and then formatting returning it further as JSON. You would need to store non-JSON data to do it the way you want. Therefore it seems easier to treat it as a string:
SELECT t.id,
'{' + STRING_AGG( '"' +t.typee + '": ' + t.jsonStr,',') WITHIN GROUP (ORDER BY typee) + '}'
FROM t
GROUP BY t.id;
Using FOR XML PATH:
SELECT t1.id,
'{' + STUFF((SELECT ',"' + t2.typee + '": ' + t2.jsonStr
FROM t t2
WHERE t2.Id = t1.id
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,1,'') + '}'
FROM t t1
GROUP BY t1.id;