SQL Server: обновите JSON с другого JSON - обычно - PullRequest
0 голосов
/ 06 июня 2018

Как в SQL Server обновить / объединить json с другим json, без явного использования ключей / определяющих столбцов?

Некоторые сведения: метаданные хранятся в виде json в столбце varchar(max).Каждая запись может иметь разные ключи метаданных в одной и той же таблице.Как хранить людей и продукты в одной таблице.Аналогично модель данных EAV , но вместо таблицы значений я использую столбец json для хранения метаданных в виде пар ключ-значение.Вот почему я ищу универсальное решение.

, т.е. одна запись может иметь метаданные

{"last_name":"John","first_name":"Smith","age":28,"Address":"123 Steels st…"}

другая запись в той же таблице может иметь метаданные

{"product_name":"Box","material":"plastic","Price":1.5,"Weight":20,"Height":15}

Я ищу эффективный / современный способ обновления / добавления нескольких значений в json, из json.

то есть источник

{
    "last_name": "John",
    "first_name": "Smith",
    "age": 28,
    "weight":79
    "address": "123 Steels st…"
}

что обновить / добавить:

{   
    "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

Мое решение работает, но:

  1. Вероятно, не будет работать с массивами или вложенным json.Хорошо, меня это не беспокоит.

  2. Интересно, есть ли более правильный / эмоциональный / элегантный способ сделать все решение, возможно, с использованием json_modify?

  3. Порядок пар ключ-значение не сохраняется как источник, но я думаю, что это не имеет большого значения.

  4. Любой нормальный способ преобразования ключа-значение таблицы обратно в json без явного определения столбцов и без «мусора», который «для json auto» дает?

Код:

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"}

ОБНОВЛЕНИЕ:

Основано на Роман Пекар элегантное решение , я добавил еще один случай к этому решениючтобы исключить кавычки, когда значение [тип] = 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;

1 Ответ

0 голосов
/ 05 апреля 2019

Взгляните на этот ответ .Если вы работаете в 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;

sql fiddle demo

update обновлено на основе комментариев - лучше работать с различными типами значений

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

...