在存储过程中多次访问 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?的主要内容,如果未能解决你的问题,请参考以下文章