在存储过程中多次访问 OPENJSON 解析 JSON?

Posted

技术标签:

【中文标题】在存储过程中多次访问 OPENJSON 解析 JSON?【英文标题】:Accessing OPENJSON parsed JSON multiple times in a stored procedure? 【发布时间】:2020-12-30 23:19:55 【问题描述】:

考虑一下这个 JSON:


    "Name": "Alice",
    "Relations": [
        
            "RelationId": 1,
            "Value": "one"
        ,
        
            "RelationId": 2,
            "Value": "two"
        
    ]

我将此 JSON 传递给一个存储过程,在该存储过程中对其进行解析并插入名称:

-- parse JSON
WITH [source]
AS (SELECT *
    FROM
        OPENJSON(@json)
        WITH
        (
            [Name] VARCHAR(50),
            [Relations] VARCHAR(MAX)
        ) -- end json WITH
) -- end WITH [source] AS

-- insert Name
INSERT INTO dbo.names
(
    [Name]
)
SELECT [s].[Name]
FROM [source] s;

接下来,我要插入关系,所以首先我必须 OPENJSON [Relations] 部分:

WITH [relationsSource]
AS (SELECT *
    FROM
        -- now, here is the problem: the CTE (common table expression)
        -- named [source] isn't available anymore
        OPENJSON(<how to access [source].[Relations] here?)
        WITH
        (
            [RelationId] INT,
            [Value] VARCHAR(50)
        ) -- end json WITH
) -- end WITH [relationsSource]

我知道我可以做类似OPENJSON(@json, '$Relations') 的事情。但这会再次解析整个@json 以搜索$Relations 路径,而不是仅解析先前提取的[source].[Relations]

有什么解决方案可以让我使用类似的东西

OPENJSON([source].[Relations]) -- pass only the Relations subset of @json

这样OPENJSON 就不必再次解析完整的@json 了吗?

【问题讨论】:

【参考方案1】:

试试这个:

DECLARE @Json NVARCHAR(MAX) = N'

    "Name": "Alice",
    "Relations": [
        
            "RelationId": 1,
            "Value": "one"
        ,
        
            "RelationId": 2,
            "Value": "two"
        
    ]

'

SELECT persons.Id PersonId
    , persons.Name
    , relations.RelationId
    , relations.Value
INTO #JsonToFlatTable
FROM (
    -- sub-query to retrieve the root person.Name and the array of relations
    SELECT *
    , Row_Number() OVER (ORDER BY Name) Id -- Add a fake ID or use some kind of mapping with an existing table. 
    FROM OPENJSON(@json, N'lax $') 
    WITH (
        [Name] VARCHAR(255) N'lax $.Name'
        , Relations nvarchar(MAX) N'$.Relations' AS JSON  
    )
) persons
-- Use openjson on the subset of relations to retrieve the RelationId and Value
CROSS APPLY OPENJSON(persons.Relations, N'lax $')
    WITH(
        RelationId INT N'lax $.RelationId'
        , Value VARCHAR(255) N'lax $.Value'
    ) relations 

-- Maybe set IDENTITY_INSERT ON
INSERT INTO Person(Id, Name)
SELECT DISTINCT PersonId
    , Name
FROM #JsonToFlatTable
-- Maybe set IDENTITY_INSERT OFF

INSERT INTO Relation(PersonId, RelationId, Value)
SELECT PersonId
    , RelationId
    , Value
FROM #JsonToFlatTable

输出

PersonId Name RelationId Value
1 Alice 1 one
1 Alice 2 two

您使用WITH 子句指定schema。并且您将只解析与OPENJSON 的子集关系。

Relations nvarchar(MAX) N'$.Relations' AS JSON 线上的AS JSON 对此工作至关重要。如果未指定 AS JSON,您将获得 NULL 而不是数组。

来自上面的微软文档:

注意 AS JSON 子句如何导致值作为 JSON 对象返回,而不是 col5 和 array_element 中的标量值。

如果数组大小是动态的,则不可能一次性解析所有内容。如果它是固定大小,您可以为每个已知索引创建列,但这不可维护。当数组大小发生变化时,您需要更改代码。

然后将提取的数据放入相应的表中,您可以使用临时表并填充相应的表。您可能需要Identity_Insert。或者您可以使用output clause 从persons 表中检索生成的ID。

【讨论】:

感谢您的详细回复!不幸的是,它告诉我,我可能已经过多地减少了我的问题。我需要在两个不同的表中插入数据,所以Name按照给定的dbo.names 进入,但是对于关系,还有第二个表dbo.relations 并且每一行都需要(,,)。不确定我是否应该提出一个新问题或修改我现有的问题):同时,我想知道 Relations nvarchar(MAX) N'$.Relations' AS JSON 是否还没有解析关系(因为它们作为 JSON 对象返回)? 另外,您知道为什么关系需要AS JSON 吗?JSON 作为字符串和带有AS JSON 的JSON 有什么区别?【参考方案2】:

你可以试试这样的。 JSON_VALUE 函数选择“名称”的单个实例。 OPENJSON tvf 指定“关系”对象的路径并提供列定义。

declare @json nvarchar(max) = N'

    "Name": "Alice",
    "Relations": [
        
            "RelationId": 1,
            "Value": "one"
        ,
        
            "RelationId": 2,
            "Value": "two"
        
    ]
'


select json_value(@json, '$.Name') as [Name], j.*
from openjson(@json, '$.Relations') 
              with (RelationId          int,
                    [Value]             nvarchar(4000)) j;

输出

Name    RelationId  Value
Alice   1           one
Alice   2           two

【讨论】:

以上是关于在存储过程中多次访问 OPENJSON 解析 JSON?的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 2016 中存储过程中的 OPENJSON 语法

OPENJSON 在解析 JSON 属性时忽略大小写

OPENJSON 与 NULL 值交叉应用 (SQL)

使用 OPENJSON 解析

SSDT - 交叉应用 OPENJSON

使用 OPENJSON 跳过 OBJECTS 解析 JSON SQL