将嵌套的json数组插入sql server中的多个表中

Nam*_*yal 1 sql-server json sql-server-2016

我在 Sql 服务器中有以下 Json 对象。我想将此数据插入多个表及其关系(即外键):

DECLARE @JsonObject NVARCHAR(MAX) = N'{  
   "FirstElement":{  
      "Name":"ABC",
      "Location":"East US",
      "Region":"West US",
      "InnerElement":[
         {  
            "Name":"IE1",
            "Description":"IE1 Description",
            "Type":"Small",
            "InnerMostElement":[  
               {
                  "Key":"Name",
                  "Value":"IME1"
               },
                {
                  "Key":"AnotherProperty",
                  "Value":"Value1"
               }
            ]
         },
         {  
            "Name":"IE2",
            "Description":"IE2 Description",
            "Type":"Medium",
            "InnerMostElement":[ 
               {
                  "Key":"Name",
                  "Value":"IME2"
               },
                {
                  "Key":"Address",
                  "Value":"Xyz"
               }, 
               {
                  "Key":"Type",
                  "Value":"Simple"
               },
                {
                  "Key":"LastProperty",
                  "Value":"ValueX"
               }
            ]
         }
      ]
   }
}'
Run Code Online (Sandbox Code Playgroud)

表结构附在这里:

在此处输入图片说明

我要插入FirstElement在数据表1InnerElement在数据表2InnerMostElement数据表3

Jer*_*ert 6

最简单的部分是第一个表,因为我们只插入一行并且它没有依赖项:

BEGIN TRANSACTION;

INSERT Table1([Name], [Location], [Region])
SELECT [Name], [Location], [Region]
FROM OPENJSON(@JsonObject, '$.FirstElement')
WITH (
    [Name] VARCHAR(100),
    [Location] VARCHAR(100),
    [Region] VARCHAR(100)
);

DECLARE @Table1Id INT = SCOPE_IDENTITY();
Run Code Online (Sandbox Code Playgroud)

困难的部分是下一张桌子。我们需要捕获插入行的所有标识,还要捕获所有尚未插入表 3 的数据。 因为 的OUTPUT子句INSERT仅限于输出基表中的值,所以我们需要使用MERGE技巧:

DECLARE @Table3Input TABLE([Table2Id] INT, [InnerMostElement] NVARCHAR(MAX));

MERGE Table2
USING (
    SELECT [Name], [Description], [Type], [InnerMostElement]
    FROM OPENJSON(@JsonObject, '$.FirstElement.InnerElement')
    WITH (
        [Name] VARCHAR(100),
        [Description] VARCHAR(100),
        [Type] VARCHAR(100),
        [InnerMostElement] NVARCHAR(MAX) AS JSON
    )
) AS J
ON 1 = 0    -- Always INSERT
WHEN NOT MATCHED THEN 
    INSERT([Table1Id], [Name], [Description], [Type])
    VALUES (@Table1Id, J.[Name], J.[Description], J.[Type])
    OUTPUT inserted.Id, J.[InnerMostElement]
    INTO @Table3Input([Table2Id], [InnerMostElement]);
Run Code Online (Sandbox Code Playgroud)

如果主要使用 JSON 填充表,则使用SEQUENCE对象生成连续值(使用sp_sequence_get_range)可能更方便,而无需将整个 JSON 捕获到临时表中。这将大大简化这一过程并消除对MERGE.

最后一个表又简单了:

INSERT Table3([Table2Id], [Key], [Value])
SELECT [Table2Id], KV.[Key], KV.[Value]
FROM @Table3Input CROSS APPLY (
    SELECT [Key], [Value]
    FROM OPENJSON([InnerMostElement])
    WITH (
        [Key] VARCHAR(100),
        [Value] VARCHAR(100)
    )
) AS KV;

COMMIT;
Run Code Online (Sandbox Code Playgroud)

该事务在逻辑上是确保该对象被完全插入或根本不插入的必要条件。

最终输出:

BEGIN TRANSACTION;

INSERT Table1([Name], [Location], [Region])
SELECT [Name], [Location], [Region]
FROM OPENJSON(@JsonObject, '$.FirstElement')
WITH (
    [Name] VARCHAR(100),
    [Location] VARCHAR(100),
    [Region] VARCHAR(100)
);

DECLARE @Table1Id INT = SCOPE_IDENTITY();
Run Code Online (Sandbox Code Playgroud)
DECLARE @Table3Input TABLE([Table2Id] INT, [InnerMostElement] NVARCHAR(MAX));

MERGE Table2
USING (
    SELECT [Name], [Description], [Type], [InnerMostElement]
    FROM OPENJSON(@JsonObject, '$.FirstElement.InnerElement')
    WITH (
        [Name] VARCHAR(100),
        [Description] VARCHAR(100),
        [Type] VARCHAR(100),
        [InnerMostElement] NVARCHAR(MAX) AS JSON
    )
) AS J
ON 1 = 0    -- Always INSERT
WHEN NOT MATCHED THEN 
    INSERT([Table1Id], [Name], [Description], [Type])
    VALUES (@Table1Id, J.[Name], J.[Description], J.[Type])
    OUTPUT inserted.Id, J.[InnerMostElement]
    INTO @Table3Input([Table2Id], [InnerMostElement]);
Run Code Online (Sandbox Code Playgroud)
INSERT Table3([Table2Id], [Key], [Value])
SELECT [Table2Id], KV.[Key], KV.[Value]
FROM @Table3Input CROSS APPLY (
    SELECT [Key], [Value]
    FROM OPENJSON([InnerMostElement])
    WITH (
        [Key] VARCHAR(100),
        [Value] VARCHAR(100)
    )
) AS KV;

COMMIT;
Run Code Online (Sandbox Code Playgroud)

为了完整起见,您可以通过以下方式将其转回 JSON:

SELECT 
  [Name] AS 'FirstElement.Name', 
  [Location] AS 'FirstElement.Location', 
  [Region] AS 'FirstElement.Region',
  (
    SELECT 
      [Name], 
      [Description], 
      [Type],
      (
        SELECT 
          [Key], 
          [Value]
        FROM Table3
        WHERE Table3.Table2Id = Table2.Id
        FOR JSON PATH
      ) AS 'InnerMostElement'
    FROM Table2
    WHERE Table2.Table1Id = Table1.Id
    FOR JSON PATH
  ) AS 'FirstElement.InnerElement'
FROM Table1
FOR JSON PATH;
Run Code Online (Sandbox Code Playgroud)