Sql server 带动态查询的嵌套JSON

Sql server 带动态查询的嵌套JSON,sql-server,json,vb.net,nested,dynamic-sql,Sql Server,Json,Vb.net,Nested,Dynamic Sql,我正在寻找最好的解决方案,通过动态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

我正在寻找最好的解决方案,通过动态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是tblTableName中每个记录的Id

IDParent是tblTableName中记录的父Id

此递归函数仅在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;
但是所有的修改都导致了相同的错误:“只有函数和一些扩展存储过程可以从函数中执行。”

我从vb.net调用SQL server,以便创建一个附加函数来对嵌套的JSON进行树分析,但这是我的最后一个选项。我认为最快速、最干净的解决方案是在(T)SQL中完成嵌套

那么,是否有人可以帮助我创建一个能够支持动态应用程序的解决方案 SQL并返回嵌套的JSON

谢谢,谢谢你的帮助


阿诺

谢谢塞普皮奇的回答

我将函数从最初的post转换为存储过程,但在转换这一部分时遇到了困难:

SET @Json =  
    (SELECT *, JSON_QUERY(dbo.NestedJSON(Id)) AS child
    FROM    dbo.tblTableName
    WHERE   IDParent is NULL
    FOR JSON AUTO);
存储过程不能在查询定义本身中使用,因为它不会以相同的方式返回返回值。因为它不能在查询本身中使用,所以我不能传递参数Id(dbo.NestedJSON(Id))

我能想到的唯一解决方案是循环所有查询结果,逐个获取每个ID,然后执行它

还有其他建议吗


阿诺

谢谢塞普皮奇的回答

我将函数从最初的post转换为存储过程,但在转换这一部分时遇到了困难:

SET @Json =  
    (SELECT *, JSON_QUERY(dbo.NestedJSON(Id)) AS child
    FROM    dbo.tblTableName
    WHERE   IDParent is NULL
    FOR JSON AUTO);
存储过程不能在查询定义本身中使用,因为它不会以相同的方式返回返回值。因为它不能在查询本身中使用,所以我不能传递参数Id(dbo.NestedJSON(Id))

我能想到的唯一解决方案是循环所有查询结果,逐个获取每个ID,然后执行它

还有其他建议吗


阿诺我终于解决了这个问题。对于感兴趣的人,这里有一个解决方案:

第一步是创建可在递归例程中使用的新表类型:

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
)
在第二步中,我更改了原始区域递归函数以使用此表定义:

-- =============================================
-- 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结果,必须将查询结果转换为JSONCTETableType。这在以下CTE中完成,该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;
祝你好运


阿诺我终于解决了这个问题。对于感兴趣的人,这里有一个解决方案:

第一步是创建可在递归例程中使用的新表类型:

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
)
在第二步中,我更改了原始区域递归函数以使用此表定义:

-- =============================================
-- 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结果,必须将查询结果转换为JSONCTETableType。这在以下CTE中完成,该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;
祝你好运


Arno

您可以使用存储过程而不是函数谢谢您的建议。我还没有想到远程进程。远程过程是否与函数一样快?如果您的sp执行相同的操作,它将具有相同的执行时间。区别在于您接收结果的方式:Function result可以直接用于查询,sp result应该先保存,然后才能使用。您可以使用存储过程而不是Function谢谢您的建议。我还没有想到远程进程。远程过程是否与函数一样快?如果您的sp执行相同的操作,它将具有相同的执行时间。区别在于您接收结果的方式:Function result可以直接用于查询,sp result应该先保存,然后才能使用