SQL 服务器:从另一个 json 更新 json – 一般
SQL Server : update json from another json – generically
在 SQL 服务器中,如何在不显式使用键/定义列的情况下 update/merge 一个 json 与另一个 json?
一些背景:我将元数据作为 json 存储在 varchar(max)
列中。每条记录在同一个 table 中可以有不同的元数据键。就像将人和产品存储在同一个 table 中一样。与 EAV data model 类似,但我使用 json 列代替值 table 将元数据存储为键值对。
这就是为什么我正在寻找 通用 解决方案。
即一条记录可以有元数据
{"last_name":"John","first_name":"Smith","age":28,"Address":"123 Steels st…"}
同一 table 中的另一条记录可以有元数据
{"product_name":"Box","material":"plastic","Price":1.5,"Weight":20,"Height":15}
我正在寻找一种 efficient/modern 方法来从 json 中 update/add 多个值。
即来源
{
"last_name": "John",
"first_name": "Smith",
"age": 28,
"weight":79
"address": "123 Steels st…"
}
什么 update/add:
{
"address": "567 Yonge Ave…"
"last_name": "Johnny"
"age": 35
"height":1.83
}
结果源更新为:
{
"last_name":"Smith",
"first_name": "Johnny", - updated
"age": 35, - updated
"weight":79
"address": "567 Yonge Ave…" - updated
"height":1.83 - added
}
我的解决方案:
declare @j_source varchar(200) = '{"first_name": "Smith", "last_name": "Smith","age": 28,"weight":79,"address": "123 Steels st…"}'
declare @j_update varchar(200) = '{"address": "567 Yonge Ave…","first_name": "Johnny","age": 35, "height":1.83}'
print @j_source
print @j_update
-- transform json to tables
select *
into #t_source
from openjson(@j_source)
select *
into #t_update
from openjson(@j_update)
-- combine the updated values with new values with non-updated values
select *
into #t_result
from
(
-- get key values that are not being updated
select ts.[key],ts.[value],ts.[type]
from #t_source as ts
left join #t_update as tu
on ts.[key] = tu.[key]
where tu.[key] is null
union -- get key values that are being updated. side note: the first and second select can be combined into one using isnull
select ts.[key],tu.[value],ts.[type] -- take value from #t_update
from #t_source as ts
inner join #t_update as tu
on ts.[key] = tu.[key]
union -- add new key values that does not exists in the source
select tu.[key],tu.[value],tu.[type] -- take value from #t_update
from #t_source as ts
right join #t_update as tu
on ts.[key] = tu.[key]
where ts.[key] is null
) as x
where [value] != '' -- remove key-value pair if the value is empty
/*
openjson type column data type
https://docs.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-2017
type data-type
0 null
1 string
2 int
3 true/false
4 array
5 object
*/
-- transform table back to json in a generic way
select @j_source =
'{' +
STUFF((
select replace(',"x":','x', cast([key] as varchar(4000)) COLLATE SQL_Latin1_General_CP1_CI_AS)
+ case [type]
when 1 then replace('"z"','z',[value]) -- this is a string this is a text use double-quotes
when 2 then [value] -- this is int, don't use double-quotes
else ''
end
from #t_result
for xml PATH('')
), 1, 1, '')
+ '}'
print 'after update'
print @j_source
drop table #t_source
drop table #t_update
drop table #t_result
我的解决方案有效,但是:
可能不适用于数组或嵌套 json。好的,现在不要打扰我。
我想知道是否有更多 proper/affective/elegant 方法来完成整个解决方案,也许使用 json_modify ?
键值对的顺序没有保留作为来源,但我想这没什么大不了的。
在没有明确定义列且没有 "for json auto" 给出的 "garbage" 的情况下,将键值 table 转换回 json 的任何正常方法?
代码:
SELECT [key], [value]
FROM t_result
FOR JSON path, WITHOUT_ARRAY_WRAPPER
输出:
{"key":"address","value":"567 Yonge Ave…"},
{"key":"age","value":35}, {"key":"first_name","value":"Johnny"},
{"key":"height","value":1.83},{"key":"last_name","value":"Smith"}
更新:
基于 Roman Pekar ,我在该解决方案中添加了另一种情况,以在值为 [type] = 2(int) 时排除引号。当像我这样有数百万条记录时,额外的引号会影响存储。
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 (1,3) then json_modify(@a, concat('$.',d.[key]), d.[value])
else @a
end,
@a = case
when d.[type] in (2) and TRY_CAST(d.[value] AS int) is not null then json_modify(@a, concat('$.',d.[key]), cast(d.[value] as int))
when d.[type] in (2) and TRY_CAST(d.[value] AS int) is null then json_modify(@a, concat('$.',d.[key]), d.[value])
else @a
end,
@a = case
when d.[type] in (4,5) then json_modify(@a, concat('$.',d.[key]), json_query(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;
看看this answer。
如果您在 Sql Server 2017 中工作,那么您可以创建函数来合并 json:
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;
更新 根据评论更新 - 应该更好地处理不同类型的值
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 服务器中,如何在不显式使用键/定义列的情况下 update/merge 一个 json 与另一个 json?
一些背景:我将元数据作为 json 存储在 varchar(max)
列中。每条记录在同一个 table 中可以有不同的元数据键。就像将人和产品存储在同一个 table 中一样。与 EAV data model 类似,但我使用 json 列代替值 table 将元数据存储为键值对。
这就是为什么我正在寻找 通用 解决方案。
即一条记录可以有元数据
{"last_name":"John","first_name":"Smith","age":28,"Address":"123 Steels st…"}
同一 table 中的另一条记录可以有元数据
{"product_name":"Box","material":"plastic","Price":1.5,"Weight":20,"Height":15}
我正在寻找一种 efficient/modern 方法来从 json 中 update/add 多个值。
即来源
{
"last_name": "John",
"first_name": "Smith",
"age": 28,
"weight":79
"address": "123 Steels st…"
}
什么 update/add:
{
"address": "567 Yonge Ave…"
"last_name": "Johnny"
"age": 35
"height":1.83
}
结果源更新为:
{
"last_name":"Smith",
"first_name": "Johnny", - updated
"age": 35, - updated
"weight":79
"address": "567 Yonge Ave…" - updated
"height":1.83 - added
}
我的解决方案:
declare @j_source varchar(200) = '{"first_name": "Smith", "last_name": "Smith","age": 28,"weight":79,"address": "123 Steels st…"}'
declare @j_update varchar(200) = '{"address": "567 Yonge Ave…","first_name": "Johnny","age": 35, "height":1.83}'
print @j_source
print @j_update
-- transform json to tables
select *
into #t_source
from openjson(@j_source)
select *
into #t_update
from openjson(@j_update)
-- combine the updated values with new values with non-updated values
select *
into #t_result
from
(
-- get key values that are not being updated
select ts.[key],ts.[value],ts.[type]
from #t_source as ts
left join #t_update as tu
on ts.[key] = tu.[key]
where tu.[key] is null
union -- get key values that are being updated. side note: the first and second select can be combined into one using isnull
select ts.[key],tu.[value],ts.[type] -- take value from #t_update
from #t_source as ts
inner join #t_update as tu
on ts.[key] = tu.[key]
union -- add new key values that does not exists in the source
select tu.[key],tu.[value],tu.[type] -- take value from #t_update
from #t_source as ts
right join #t_update as tu
on ts.[key] = tu.[key]
where ts.[key] is null
) as x
where [value] != '' -- remove key-value pair if the value is empty
/*
openjson type column data type
https://docs.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-2017
type data-type
0 null
1 string
2 int
3 true/false
4 array
5 object
*/
-- transform table back to json in a generic way
select @j_source =
'{' +
STUFF((
select replace(',"x":','x', cast([key] as varchar(4000)) COLLATE SQL_Latin1_General_CP1_CI_AS)
+ case [type]
when 1 then replace('"z"','z',[value]) -- this is a string this is a text use double-quotes
when 2 then [value] -- this is int, don't use double-quotes
else ''
end
from #t_result
for xml PATH('')
), 1, 1, '')
+ '}'
print 'after update'
print @j_source
drop table #t_source
drop table #t_update
drop table #t_result
我的解决方案有效,但是:
可能不适用于数组或嵌套 json。好的,现在不要打扰我。
我想知道是否有更多 proper/affective/elegant 方法来完成整个解决方案,也许使用 json_modify ?
键值对的顺序没有保留作为来源,但我想这没什么大不了的。
在没有明确定义列且没有 "for json auto" 给出的 "garbage" 的情况下,将键值 table 转换回 json 的任何正常方法?
代码:
SELECT [key], [value]
FROM t_result
FOR JSON path, WITHOUT_ARRAY_WRAPPER
输出:
{"key":"address","value":"567 Yonge Ave…"},
{"key":"age","value":35}, {"key":"first_name","value":"Johnny"},
{"key":"height","value":1.83},{"key":"last_name","value":"Smith"}
更新:
基于 Roman Pekar
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 (1,3) then json_modify(@a, concat('$.',d.[key]), d.[value])
else @a
end,
@a = case
when d.[type] in (2) and TRY_CAST(d.[value] AS int) is not null then json_modify(@a, concat('$.',d.[key]), cast(d.[value] as int))
when d.[type] in (2) and TRY_CAST(d.[value] AS int) is null then json_modify(@a, concat('$.',d.[key]), d.[value])
else @a
end,
@a = case
when d.[type] in (4,5) then json_modify(@a, concat('$.',d.[key]), json_query(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;
看看this answer。 如果您在 Sql Server 2017 中工作,那么您可以创建函数来合并 json:
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;
更新 根据评论更新 - 应该更好地处理不同类型的值
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