嵌套 JSON 与动态查询
Nested JSON with Dynamic Query
我正在寻找使用动态 SQL 查询直接从 (T)SQL 创建嵌套 JSON 字符串的最佳解决方案。
在 SQL Server 2016 中,可以使用如下语句轻松创建扁平 JSON 字符串:
SELECT *
FROM tblTableName
FOR JSON AUTO
如果您需要更复杂的嵌套结果,您可以使用简单的递归例程,例如:
CREATE FUNCTION [dbo].[NestedJSON](@Id uniqueidentifier)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE @Json NVARCHAR(MAX) = '{}';
IF @Id Is NULL
SET @Json =
(SELECT *, JSON_QUERY(dbo.NestedJSON(Id) ) AS child
FROM dbo.tblTableName
WHERE IDParent is NULL
FOR JSON AUTO);
ELSE
SET @Json =
(SELECT *, JSON_QUERY(dbo.NestedJSON(Id) ) AS child
FROM dbo.tblTableName
WHERE IDParent = @Id
FOR JSON AUTO);
RETURN @Json
END
Id is the ID of each record in tblTableName
IDParent is the parent Id of the record in tblTableName
此递归函数仅在 SQL-查询固定时有效。
在我的情况下,我有很多嵌套结构的查询。为了支持所有嵌套的 SQL 查询,我尝试修改上面的 NestedJSON 函数,但接缝是不允许在函数中使用动态 SQL 。我尝试了以下选项:
IF @Id Is NULL
Set @SQL = 'SELECT @Json=(SELECT ' + @FieldList + ' ,JSON_QUERY(dbo.MenuNested(' + @Id + ') ) AS Child FROM ' + @TheTables + ' WHERE IDParent is NULL FOR JSON AUTO)'
ELSE
Set @SQL = 'SELECT @Json=(SELECT ' + @FieldList + ' ,JSON_QUERY(dbo.MenuNested(' + @Id + ') ) AS Child FROM ' + @TheTables + ' WHERE IDParent = ' + @Id + ' FOR JSON AUTO)'
Exec(@SQL)
--or
execute sp_executesql @SQL;
但所有修改都导致相同的错误:"Only functions and some extended stored procedures can be executed from within a function."
我从 vb.net 调用 SQL 服务器,这样我就可以创建一个额外的函数来树化嵌套的 JSON,但这是我最后的选择。我认为最快速和最干净的解决方案是在 (T)SQL.
中进行完全嵌套
所以,有没有人可以帮助我创建一个能够支持动态的解决方案
SQL 和 return 嵌套 JSON?
感谢任何帮助。
阿尔诺
感谢 sepupic 的回答。
我把函数从最初的 post 转换成存储过程,但我正在努力转换这部分:
SET @Json =
(SELECT *, JSON_QUERY(dbo.NestedJSON(Id)) AS child
FROM dbo.tblTableName
WHERE IDParent is NULL
FOR JSON AUTO);
存储过程不能在查询定义本身中使用,因为它不会以相同的方式 return return 值。因为它不能在查询本身中使用,所以我不能传递参数 Id (dbo.NestedJSON(Id)).
我能想到的唯一解决办法就是循环所有的查询结果,一个一个的获取每个ID然后执行。
还有其他建议吗?
阿诺
我终于解决了这个问题。对于那些感兴趣的人,这里是解决方案:
第一步是创建一个可以在递归例程中使用的新 table 类型:
CREATE TYPE [dbo].[JSONCTETableType] AS TABLE(
[Sequence] [uniqueidentifier] NULL,
[ParentSequence] [uniqueidentifier] NULL,
[JSON] [nvarchar](max) NULL,
[IndentLevel] [int] NULL,
[WBS] [nvarchar](512) NULL,
[ChildCount] [int] NULL
)
在第二步中,我将原来的递归函数更改为使用此 table 定义:
-- =============================================
-- Author: Arno Voerman
-- Create date: 2017, September
-- =============================================
CREATE FUNCTION [dbo].[TREEIFY]
(
@ParentId uniqueidentifier, @JSONTABLE JSONCTETableType READONLY
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE @Json NVARCHAR(MAX);
If @ParentId Is NULL
Begin
SET @Json = (
Select JSON +
Case When ChildCount > 0
THEN ',"children":[' + dbo.treeIfy(Sequence,@JSONTABLE) + ']}'
ELSE ',"leaf":true}'
END + ',' AS 'data()'
FROM @JSONTABLE
Where ParentSequence Is Null
FOR XML PATH('')
)
END
Else
Begin
SET @Json = (
Select JSON +
Case When ChildCount > 0
THEN ',"children":[' + dbo.treeIfy(Sequence,@JSONTABLE) + ']}'
ELSE ',"leaf":true}'
END + ',' AS 'data()'
FROM @JSONTABLE
Where ParentSequence = @ParentId
FOR XML PATH('')
)
End
RETURN @Json
END
要从查询中获取嵌套的 JSON-result,必须将查询结果转换为 JSONCTETableType。这是在以下 CTE 中完成的,它还会生成 JSON 和我的程序需要的一些额外数据:
DECLARE @JSONTT as JSONCTETableType;
WITH JSONCTETable AS (
SELECT Sequence, IDParentSequence As ParentSequence,
(SELECT * FROM YourTable Where Sequence=anchor.Sequence FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER) As JSON,
0 as IndentLevel,
Cast(ROW_NUMBER() OVER(ORDER BY mnu_Order ASC) As NVARCHAR(512)) AS WBS
FROM YourTable AS anchor
WHERE IDParentSequence is null
UNION ALL -------------------------------------------------------------------
SELECT recur.Sequence, recur.IDParentSequence As ParentSequence,
(SELECT * FROM YourTable Where Sequence=recur.Sequence FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER) As JSON,
cte.IndentLevel + 1,
Cast(cte.WBS + '.' + Cast(ROW_NUMBER() OVER(ORDER BY mnu_Order ASC) As NVARCHAR(10)) AS NVARCHAR(512)) AS WBS
FROM YourTable AS recur
INNER JOIN JSONCTETable AS cte ON cte.Sequence = recur.IDParentSequence
)INSERT INTO @JSONTT (Sequence, ParentSequence, JSON, IndentLevel, WBS, ChildCount)
SELECT *, (Select count(*) From JSONCTETable tmpCTE Where tmpCTE.ParentSequence=JSONCTETable.Sequence) as ChildCount
FROM JSONCTETable
ORDER BY WBS;
--Remove last character ("}") of all JSON. Needed to simplify the add "CHILD"
Update @JSONTT SET JSON = stuff(JSON,len(JSON),1,'');
最后一步很简单:
Select '[' + dbo.treeIfy(Null,@JSONTT) + ']' as data, (Select count(Sequence) From @JSONTT) as totRows;
祝你好运!
阿尔诺
我正在寻找使用动态 SQL 查询直接从 (T)SQL 创建嵌套 JSON 字符串的最佳解决方案。
在 SQL Server 2016 中,可以使用如下语句轻松创建扁平 JSON 字符串:
SELECT *
FROM tblTableName
FOR JSON AUTO
如果您需要更复杂的嵌套结果,您可以使用简单的递归例程,例如:
CREATE FUNCTION [dbo].[NestedJSON](@Id uniqueidentifier)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE @Json NVARCHAR(MAX) = '{}';
IF @Id Is NULL
SET @Json =
(SELECT *, JSON_QUERY(dbo.NestedJSON(Id) ) AS child
FROM dbo.tblTableName
WHERE IDParent is NULL
FOR JSON AUTO);
ELSE
SET @Json =
(SELECT *, JSON_QUERY(dbo.NestedJSON(Id) ) AS child
FROM dbo.tblTableName
WHERE IDParent = @Id
FOR JSON AUTO);
RETURN @Json
END
Id is the ID of each record in tblTableName
IDParent is the parent Id of the record in tblTableName
此递归函数仅在 SQL-查询固定时有效。
在我的情况下,我有很多嵌套结构的查询。为了支持所有嵌套的 SQL 查询,我尝试修改上面的 NestedJSON 函数,但接缝是不允许在函数中使用动态 SQL 。我尝试了以下选项:
IF @Id Is NULL
Set @SQL = 'SELECT @Json=(SELECT ' + @FieldList + ' ,JSON_QUERY(dbo.MenuNested(' + @Id + ') ) AS Child FROM ' + @TheTables + ' WHERE IDParent is NULL FOR JSON AUTO)'
ELSE
Set @SQL = 'SELECT @Json=(SELECT ' + @FieldList + ' ,JSON_QUERY(dbo.MenuNested(' + @Id + ') ) AS Child FROM ' + @TheTables + ' WHERE IDParent = ' + @Id + ' FOR JSON AUTO)'
Exec(@SQL)
--or
execute sp_executesql @SQL;
但所有修改都导致相同的错误:"Only functions and some extended stored procedures can be executed from within a function."
我从 vb.net 调用 SQL 服务器,这样我就可以创建一个额外的函数来树化嵌套的 JSON,但这是我最后的选择。我认为最快速和最干净的解决方案是在 (T)SQL.
中进行完全嵌套所以,有没有人可以帮助我创建一个能够支持动态的解决方案 SQL 和 return 嵌套 JSON?
感谢任何帮助。
阿尔诺
感谢 sepupic 的回答。
我把函数从最初的 post 转换成存储过程,但我正在努力转换这部分:
SET @Json =
(SELECT *, JSON_QUERY(dbo.NestedJSON(Id)) AS child
FROM dbo.tblTableName
WHERE IDParent is NULL
FOR JSON AUTO);
存储过程不能在查询定义本身中使用,因为它不会以相同的方式 return return 值。因为它不能在查询本身中使用,所以我不能传递参数 Id (dbo.NestedJSON(Id)).
我能想到的唯一解决办法就是循环所有的查询结果,一个一个的获取每个ID然后执行。
还有其他建议吗?
阿诺
我终于解决了这个问题。对于那些感兴趣的人,这里是解决方案:
第一步是创建一个可以在递归例程中使用的新 table 类型:
CREATE TYPE [dbo].[JSONCTETableType] AS TABLE(
[Sequence] [uniqueidentifier] NULL,
[ParentSequence] [uniqueidentifier] NULL,
[JSON] [nvarchar](max) NULL,
[IndentLevel] [int] NULL,
[WBS] [nvarchar](512) NULL,
[ChildCount] [int] NULL
)
在第二步中,我将原来的递归函数更改为使用此 table 定义:
-- =============================================
-- Author: Arno Voerman
-- Create date: 2017, September
-- =============================================
CREATE FUNCTION [dbo].[TREEIFY]
(
@ParentId uniqueidentifier, @JSONTABLE JSONCTETableType READONLY
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE @Json NVARCHAR(MAX);
If @ParentId Is NULL
Begin
SET @Json = (
Select JSON +
Case When ChildCount > 0
THEN ',"children":[' + dbo.treeIfy(Sequence,@JSONTABLE) + ']}'
ELSE ',"leaf":true}'
END + ',' AS 'data()'
FROM @JSONTABLE
Where ParentSequence Is Null
FOR XML PATH('')
)
END
Else
Begin
SET @Json = (
Select JSON +
Case When ChildCount > 0
THEN ',"children":[' + dbo.treeIfy(Sequence,@JSONTABLE) + ']}'
ELSE ',"leaf":true}'
END + ',' AS 'data()'
FROM @JSONTABLE
Where ParentSequence = @ParentId
FOR XML PATH('')
)
End
RETURN @Json
END
要从查询中获取嵌套的 JSON-result,必须将查询结果转换为 JSONCTETableType。这是在以下 CTE 中完成的,它还会生成 JSON 和我的程序需要的一些额外数据:
DECLARE @JSONTT as JSONCTETableType;
WITH JSONCTETable AS (
SELECT Sequence, IDParentSequence As ParentSequence,
(SELECT * FROM YourTable Where Sequence=anchor.Sequence FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER) As JSON,
0 as IndentLevel,
Cast(ROW_NUMBER() OVER(ORDER BY mnu_Order ASC) As NVARCHAR(512)) AS WBS
FROM YourTable AS anchor
WHERE IDParentSequence is null
UNION ALL -------------------------------------------------------------------
SELECT recur.Sequence, recur.IDParentSequence As ParentSequence,
(SELECT * FROM YourTable Where Sequence=recur.Sequence FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER) As JSON,
cte.IndentLevel + 1,
Cast(cte.WBS + '.' + Cast(ROW_NUMBER() OVER(ORDER BY mnu_Order ASC) As NVARCHAR(10)) AS NVARCHAR(512)) AS WBS
FROM YourTable AS recur
INNER JOIN JSONCTETable AS cte ON cte.Sequence = recur.IDParentSequence
)INSERT INTO @JSONTT (Sequence, ParentSequence, JSON, IndentLevel, WBS, ChildCount)
SELECT *, (Select count(*) From JSONCTETable tmpCTE Where tmpCTE.ParentSequence=JSONCTETable.Sequence) as ChildCount
FROM JSONCTETable
ORDER BY WBS;
--Remove last character ("}") of all JSON. Needed to simplify the add "CHILD"
Update @JSONTT SET JSON = stuff(JSON,len(JSON),1,'');
最后一步很简单:
Select '[' + dbo.treeIfy(Null,@JSONTT) + ']' as data, (Select count(Sequence) From @JSONTT) as totRows;
祝你好运!
阿尔诺