繁体   English   中英

使用递归 CTE 生成嵌套的第 n 级 JSON SQL 服务器

[英]Generate nested nth level JSON SQL Server using recursive CTE

我有以下结构的数据

在此处输入图像描述

我想使用父属性 id 关系生成嵌套的 JSON 。

所需的 output。

[{
    "propertyID": 1
    , "title": "foo"
    , "class": ""
    , "typeid": 150
    , "value": "bar"
    , "children": [{}]
}, {
    "propertyID": 2
    , "title": "foo"
    , "class": ""
    , "typeid": 128
    , "value": "bar"
    , "children": [{}]
}, {
    "propertyID": 3
    , "title": "foo"
    , "class": ""
    , "typeid": 128
    , "value": "bar"
    , "children": [{
        "propertyID": 4
        , "title": "foo"
        , "class": ""
        , "typeid": 128
        , "value": "bar"
        , "children": [{
                     "propertyID": 41
                    , "title": "foo"
                    , "class": ""
                    , "typeid": 128
                    , "value": "bar"
                    , "children": [{
                     "propertyID": 411
                    , "title": "foo"
                    , "class": ""
                    , "typeid": 128
                    , "value": "bar"
                    , "children": [{}]
                  }]
                  },{
                     "propertyID": 42
                    , "title": "foo"
                    , "class": ""
                    , "typeid": 128
                    , "value": "bar"
                    , "children": [{
                     "propertyID": 421
                    , "title": "foo"
                    , "class": ""
                    , "typeid": 128
                    , "value": "bar"
                    , "children": [{}]
                  }]
                  }]
    }, {
        "propertyID": 5
        , "title": "foo"
        , "class": ""
        , "typeid": 128
        , "value": "bar"
        , "children": [{}]
    }, {
        "propertyID": 6
        , "title": "foo"
        , "class": ""
        , "typeid": 128
        , "value": "bar"
        , "children": [{
            "propertyID": 7
            , "title": "foo"
            , "class": ""
            , "typeid": 128
            , "value": "bar"
            , "children": [{
                "propertyID": 8
                , "title": "foo"
                , "class": ""
                , "typeid": 128
                , "value": "bar"
                , "children": [{
                   "propertyID": 9
                  , "title": "foo"
                  , "class": ""
                  , "typeid": 128
                  , "value": "bar"
                  , "children": [{
                     "propertyID": 10
                    , "title": "foo"
                    , "class": ""
                    , "typeid": 128
                    , "value": "bar"
                    , "children": [{}]
                  }]
                }]
            }]
        }]
    }]
}]

我在这里发现了一个类似的问题。 但我想在没有 function 的情况下做到这一点。 虽然这里很少有类似的问题,但没有什么符合我的要求。 到目前为止,我尝试了这个。 但是当同一个节点有两个孩子时它不起作用。 它复制了结果。

这是我尝试过的。

;WITH childrens AS (
 SELECT propertyID, parentID, title, children
 FROM CTE WHERE children = '[{}]'

 union ALL

 SELECT cte.propertyID, cte.parentID, cte.title, JSON_QUERY(
   (SELECT c.propertyID, c.parentID, c.title, JSON_QUERY(c.children) AS children FOR JSON 
 PATH)
 ) AS children
 FROM CTE
 INNER JOIN childrens c on CTE.propertyID = c.parentID
 ), Tree as (
 SELECT c.propertyID, c.parentID, c.title, JSON_QUERY(
  (
    SELECT ch.propertyID, ch.parentID, ch.title, JSON_QUERY(ch.children) AS children
    FROM childrens ch
    WHERE c.propertyID = ch.parentID FOR JSON PATH
  )
) AS children
FROM childrens c WHERE parentID = 0
)
select DISTINCT *
from tree FOR JSON PATH 

我得到的是

    [
   {
      "propertyID":5,
      "parentID":3,
      "title":"foo",
      "children":[
         {
            
         }
      ]
   },
   {
      "propertyID":4,
      "parentID":3,
      "title":"foo",
      "children":[
         {
            "propertyID":41,
            "parentID":4,
            "title":"foo",
            "children":[
               {
                  "propertyID":411,
                  "parentID":41,
                  "title":"foo",
                  "children":[
                     {
                        
                     }
                  ]
               }
            ]
         }
      ]
   },
   {
      "propertyID":4,
      "parentID":3,
      "title":"foo",
      "children":[
         {
            "propertyID":42,
            "parentID":4,
            "title":"foo",
            "children":[
               {
                  "propertyID":421,
                  "parentID":42,
                  "title":"foo",
                  "children":[
                     {
                        
                     }
                  ]
               }
            ]
         }
      ]
   },
   {
      "propertyID":6,
      "parentID":3,
      "title":"foo",
      "children":[
         {
            "propertyID":7,
            "parentID":6,
            "title":"foo",
            "children":[
               {
                  "propertyID":8,
                  "parentID":7,
                  "title":"foo",
                  "children":[
                     {
                        "propertyID":9,
                        "parentID":8,
                        "title":"foo",
                        "children":[
                           {
                              "propertyID":10,
                              "parentID":9,
                              "title":"foo",
                              "children":[
                                 {
                                    
                                 }
                              ]
                           }
                        ]
                     }
                  ]
               }
            ]
         }
      ]
   }
]

它复制属性= 4的节点。任何建议这样做。

sql 小提琴样品在这里

以多种不同的方式对此进行了多次讨论,在我看来,问题在于 SQL Server 无法在递归 CTE 中使用聚合,因此您无法递归聚合每一行的所有子项。

我发现的最简单的方法是(恐怖的恐怖!)一个标量用户定义函数(内联 TVF 不能递归)。

数据库<>小提琴

CREATE FUNCTION dbo.GetJson (@parentID int)
RETURNS nvarchar(max)
AS BEGIN
    RETURN (
        SELECT
          propertyID,
          title,
          typeid,
          [value],
          children = JSON_QUERY(dbo.GetJson(propertyID))
        FROM property p
        WHERE parentID = @parentID
        FOR JSON PATH
    );
END;
SELECT
  propertyID,
  title,
  typeid,
  [value],
  children = JSON_QUERY(dbo.GetJson(propertyID))
FROM property p
WHERE parentID IS NULL
FOR JSON PATH;

结果

[
  {
    "propertyID": 1,
    "title": "foo",
    "typeid": 150,
    "value": "bar"
  },
  {
    "propertyID": 2,
    "title": "foo",
    "typeid": 150,
    "value": "bar"
  },
  {
    "propertyID": 3,
    "title": "foo",
    "typeid": 150,
    "value": "bar",
    "children": [
      {
        "propertyID": 4,
        "title": "foo",
        "typeid": 150,
        "value": "bar",
        "children": [
          {
            "propertyID": 41,
            "title": "foo",
            "typeid": 150,
            "value": "bar",
            "children": [
              {
                "propertyID": 411,
                "title": "foo",
                "typeid": 150,
                "value": "bar"
              }
            ]
          },
          {
            "propertyID": 42,
            "title": "foo",
            "typeid": 150,
            "value": "bar",
            "children": [
              {
                "propertyID": 421,
                "title": "foo",
                "typeid": 150,
                "value": "bar"
              }
            ]
          }
        ]
      },
      {
        "propertyID": 5,
        "title": "foo",
        "typeid": 150,
        "value": "bar"
      },
      {
        "propertyID": 6,
        "title": "foo",
        "typeid": 150,
        "value": "bar",
        "children": [
          {
            "propertyID": 7,
            "title": "foo",
            "typeid": 150,
            "value": "bar",
            "children": [
              {
                "propertyID": 8,
                "title": "foo",
                "typeid": 150,
                "value": "bar",
                "children": [
                  {
                    "propertyID": 9,
                    "title": "foo",
                    "typeid": 150,
                    "value": "bar",
                    "children": [
                      {
                        "propertyID": 10,
                        "title": "foo",
                        "typeid": 150,
                        "value": "bar"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

这是一个递归解决方案。 它按路径顺序构造每行 json 对象,添加“子”元素,并在必要时关闭 arrays 和对象。 最终结果是单个对象的串联,包装为一个数组:

WITH 
    Recursion AS
    (
        -- Anchor part
        SELECT 
            P.propertyID, 
            depth = 1,
            rpath = CONVERT(nvarchar(4000), P.propertyID),
            has_children = 
                IIF
                (
                    EXISTS
                    (
                        SELECT C.* 
                        FROM dbo.property AS C 
                        WHERE C.parentId = P.propertyID
                    ), 
                    1, 0
                ),
            element = 
                (
                    SELECT P.propertyID, P.title, P.typeid, P.[value] 
                    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
                )
        FROM dbo.property AS P
        WHERE P.parentID = 0

        UNION ALL 

        -- Recursive part
        SELECT
            P.propertyID,
            depth = R.depth + 1,
            rpath = CONCAT_WS(N'.', R.rpath, P.propertyID),
            has_children = 
                IIF
                (
                    EXISTS
                    (
                        SELECT C.* 
                        FROM dbo.property AS C 
                        WHERE C.parentId = P.propertyID
                    ), 
                    1, 0
                ),
            element = 
                (
                    SELECT P.propertyID, P.title, P.typeid, P.[value] 
                    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
                )
        FROM Recursion AS R
        JOIN dbo.property AS P
            WITH (FORCESEEK)
            ON P.parentID = R.propertyID
    ),
    AddNextDepth AS
    (
        SELECT
            R.*,
            NextDepth = 
                LEAD(R.depth) OVER (
                    ORDER BY R.rpath)
        FROM Recursion AS R
    ),
    ModifiedTree AS
    (
        SELECT 
            ND.rpath, 
            element =
                CONCAT
                (
                    -- Insert "children" element
                    IIF
                    (
                        -- No children, add empty array
                        ND.has_children = 0,
                        STUFF
                        (
                            ND.element,
                            LEN(ND.element),
                            0,
                            N',"children":[{}]'
                        ),
                        -- Insert "children" element
                        STUFF
                        (
                            ND.element,
                            LEN(ND.element),
                            1,
                            N',"children":['
                        )
                    ),
                    -- Close previously opened array(s) if necessary
                    REPLICATE
                    (
                        N']}', 
                        -- Number of closures needed
                        ND.depth - ISNULL(ND.NextDepth, 1)
                    ),
                    -- Add comma if no children and not the last line
                    IIF
                    (
                        ND.has_children = 0 
                            AND ND.NextDepth IS NOT NULL, 
                        N',', 
                        N''
                    )
                )
        FROM AddNextDepth AS ND
    )
-- Concatenate objects in path order and add array wrapper
SELECT
    CONCAT
    (
        N'[',
        STRING_AGG(MT.element, N'') 
            WITHIN GROUP (ORDER BY MT.rpath),
        N']'
    )
FROM ModifiedTree AS MT;

Output:

[
  {
    "propertyID": 1,
    "title": "foo",
    "typeid": 150,
    "value": "bar",
    "children": [
      {}
    ]
  },
  {
    "propertyID": 2,
    "title": "foo",
    "typeid": 128,
    "value": "bar",
    "children": [
      {}
    ]
  },
  {
    "propertyID": 3,
    "title": "foo",
    "typeid": 128,
    "value": "bar",
    "children": [
      {
        "propertyID": 4,
        "title": "foo",
        "typeid": 128,
        "value": "bar",
        "children": [
          {
            "propertyID": 41,
            "title": "foo",
            "typeid": 128,
            "value": "bar",
            "children": [
              {
                "propertyID": 411,
                "title": "foo",
                "typeid": 128,
                "value": "bar",
                "children": [
                  {}
                ]
              }
            ]
          },
          {
            "propertyID": 42,
            "title": "foo",
            "typeid": 128,
            "value": "bar",
            "children": [
              {
                "propertyID": 421,
                "title": "foo",
                "typeid": 128,
                "value": "bar",
                "children": [
                  {}
                ]
              }
            ]
          }
        ]
      },
      {
        "propertyID": 5,
        "title": "foo",
        "typeid": 128,
        "value": "bar",
        "children": [
          {}
        ]
      },
      {
        "propertyID": 6,
        "title": "foo",
        "typeid": 128,
        "value": "bar",
        "children": [
          {
            "propertyID": 7,
            "title": "foo",
            "typeid": 128,
            "value": "bar",
            "children": [
              {
                "propertyID": 8,
                "title": "foo",
                "typeid": 128,
                "value": "bar",
                "children": [
                  {
                    "propertyID": 9,
                    "title": "foo",
                    "typeid": 128,
                    "value": "bar",
                    "children": [
                      {
                        "propertyID": 10,
                        "title": "foo",
                        "typeid": 128,
                        "value": "bar",
                        "children": [
                          {}
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

db<>小提琴

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM