web-dev-qa-db-ja.com

SQL Serverで2つのjsonオブジェクトを連結またはマージする

1つの列に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 を使用すると、列を1つずつ変更できます。ネストされたプロパティを含め、それらが多すぎます。

基本的に、 postgres ||連結演算子 に相当するものを探しています。

7
Jan Blaha

SQL Server 2016では、変数をjsonパスとして JSON_MODIFY で使用することはできないため、この問題のエレガントな解決策があるかどうかはわかりません。

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を使用して配列の最初の要素を追加することはできないため、最初の文字列がオブジェクトで2番目が配列の場合は、単純な CONCAT にフォールバックします。
  • 私はクリエイティブに JSON_QUERY 関数を使用して、jsonが正しく挿入されるようにしなければなりませんでした。
  • SELECT ステートメントで変数を割り当てると、代入ステートメントで変数の以前の値を使用できるという事実を使用しました。

sql server fiddle demo

postgresql fiddle example

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

3
Roman Pekar

あなたはそのコードに似た何かをすることができます:

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"}
0
Jorge Ribeiro

また、パーティーには少し遅れましたが、JSONをMS SQLにマージしようとして同様の問題に直面しました。また、これを再帰的にして、 "union"、 "concat"、 "replace"などの配列の戦略を定義できるようにしたいと考えました。

mergeJSONパス式 などのJSON操作のソリューションは、オープンソースになり、@ Github が利用可能になりました

MS SQLのJSONメソッドをさらに改善できるように、自由に使用、コメント、投稿してください。

0
Anyvado

パーティーには少し遅れますが、今は似たようなことが起こっています。トップレベルのJSONアイテムをマージする this 問題に基づいてソリューションを作成しました。

これが何をするかのいくつかの例:

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

このバージョンでは、サブアイテムをマージするためにドリルダウンしません(たとえば、2番目の例では["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), '}')
0
Impulse

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"}
0
Pawan Kumar