【问题标题】:Nested JSON with Dynamic Query带有动态查询的嵌套 JSON
【发布时间】:2017-09-24 14:44:10
【问题描述】:

我正在寻找使用动态 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-Query 固定时才有效。

在我的情况下,我有很多嵌套结构的查询。为了支持所有许多嵌套 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 服务器,因此我可以创建一个附加函数来树化嵌套的 JSON,但这是我的最后一个选择。我认为最快最干净的解决方案是在 (T)SQL 中进行完整的嵌套。

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

感谢任何帮助。

阿诺

【问题讨论】:

  • 可以使用存储过程代替函数
  • 感谢您的建议。我还没有想到远程过程。远程过程和函数一样快吗?
  • 如果你的 sp 做同样的事情,它将有同样的执行时间。不同的是你如何接收结果:funcion结果可以直接在查询中使用,sp结果需要保存后才能使用

标签: sql-server json vb.net nested dynamic-sql


【解决方案1】:

感谢 sepupic 的回答。

我将函数从最初的帖子转换为存储过程,但我在转换这部分时很吃力:

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,然后执行。

还有什么建议吗?

阿诺

【讨论】:

    【解决方案2】:

    我终于解决了这个问题。对于那些对此感兴趣的人是解决方案:

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

    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 中完成的,它还会生成我的程序需要的 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;
    

    祝你好运!

    阿诺

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多