【问题标题】:SQL Server 2016 - Build logical Hierarchy into JSON structure from single tableSQL Server 2016 - 从单个表将逻辑层次结构构建到 JSON 结构中
【发布时间】:2018-12-16 12:51:19
【问题描述】:

我有一个表格,其中包含与一个非常大的文档有关的信息。该表如下所示:

ID      | Title         | Parent_ID     | <other columns>
--------+---------------+---------------+-------------------
0       | Root          | null          | ...
1       | Introduction  | 0             | ...
2       | Glossary      | 1             | ...
3       | Audience      | 1             | ...
4       | "A"           | 2             | ...
5       | "B"           | 2             | ...
6       | "C"           | 2             | ...

结果 JSON 应如下所示(为清楚起见,省略了 &lt;other columns&gt; 部分):

{"ID"        : 0        ,
 "Title"     : "Root"   ,
 "Contents"  : [{"ID"        : 1             ,
                 "Title"     : "Introduction",
                 "Contents"  : [{"ID"          : 2           ,
                                 "Title"       : "Glossary"  ,
                                 "Contents"    : [{"ID"       : 4       ,
                                                   "Title"    : "A"     ,
                                                   "Contents" : []       },
                                                  {"ID"       : 5       ,
                                                   "Title"    : "B"     ,
                                                   "Contents" : []       },
                                                  {"ID"       : 6       ,
                                                   "Title"    : "C"     ,
                                                   "Contents" : []       }]
                                },
                                {"ID"       : 3          ,
                                 "Title"    : "Audience" ,
                                 "Contents" : []
                                }
                               ]
                },
                ....
               ]
}

我确实有一个简单的(递归)过程可以处理这个问题,但希望有一种更简单的方法使用 DBMS 的 JSON 功能(也许使用 CTE?)。

【问题讨论】:

    标签: sql json sql-server sql-server-2016


    【解决方案1】:

    如果已知父/子关系的最大深度?
    然后你可以像这个例子一样把它拉下来:

    dbfiddle here

    的测试

    测试数据:

    CREATE TABLE documentdetails 
    (
       ID INT PRIMARY KEY NOT NULL,
       Title VARCHAR(30) NOT NULL,
       Parent_ID INT,
       FOREIGN KEY (Parent_ID) REFERENCES documentdetails (ID) 
    );
    
    INSERT INTO documentdetails (ID, Title, Parent_ID) VALUES 
    (1, 'Root', null), (2, 'Introduction', 1), (3, 'Glossary', 1),
    (4, 'Audience', 1), (5, 'A', 2), (6,'B', 2), (7, 'C', 2), 
    (8, 'Foo', null), (9, 'Bar Intro', 8), (10, 'Glossy stuff', 8), (11, 'What The Fook', 8),
    (12, 'Yo', 9), (13, 'Ai', 10), (14, 'Potato', 11);
    

    查询:

    SELECT 
    root.ID, 
    root.Title,
    (  
       SELECT lvl0.ID, lvl0.Title, 0 as Depth,
       (  
          SELECT lvl1.ID, lvl1.Title, 1 as Depth,
          ( 
             SELECT lvl2.ID, lvl2.Title, 2 as Depth,
             ( 
                SELECT lvl3.ID, lvl3.Title, 3 as Depth
                FROM documentdetails lvl3
                WHERE lvl3.Parent_ID = lvl2.ID
                FOR JSON PATH
             ) AS Contents
             FROM documentdetails lvl2
             WHERE lvl2.Parent_ID = lvl1.ID
             FOR JSON PATH
          ) AS Contents
          FROM documentdetails lvl1
          WHERE lvl1.Parent_ID = lvl0.ID
          FOR JSON PATH
       ) AS Contents
       FROM documentdetails lvl0
       WHERE lvl0.ID = root.ID
       FOR JSON PATH
    ) AS Contents
    FROM documentdetails root
    WHERE root.Parent_ID IS NULL;
    

    结果:

    ID  Title   Contents
    --  -----   --------
    1   Root    [{"ID":1,"Title":"Root","Depth":0,"Contents":[{"ID":2,"Title":"Introduction","Depth":1,"Contents":[{"ID":5,"Title":"A","Depth":2},{"ID":6,"Title":"B","Depth":2},{"ID":7,"Title":"C","Depth":2}]},{"ID":3,"Title":"Glossary","Depth":1},{"ID":4,"Title":"Audience","Depth":1}]}]
    8   Foo     [{"ID":8,"Title":"Foo","Depth":0,"Contents":[{"ID":9,"Title":"Bar Intro","Depth":1,"Contents":[{"ID":12,"Title":"Yo","Depth":2}]},{"ID":10,"Title":"Glossy stuff","Depth":1,"Contents":[{"ID":13,"Title":"Ai","Depth":2}]},{"ID":11,"Title":"What The Fook","Depth":1,"Contents":[{"ID":14,"Title":"Potato","Depth":2}]}]}]
    

    如果您不知道表格中的最大深度?
    这是使用递归 CTE 来了解这一点的 SQL。

    WITH RCTE AS
    (
       SELECT ID as rootID, 0 as lvl, ID, Parent_ID
       FROM documentdetails
       WHERE Parent_ID IS NULL
    
       UNION ALL
    
       SELECT r.rootID, lvl + 1, t.ID, t.Parent_ID
       FROM RCTE r
       JOIN documentdetails t ON t.Parent_ID = r.ID
    )
    SELECT rootID, MAX(lvl) as Depth, COUNT(*) as Nodes
    FROM RCTE
    GROUP BY rootID
    ORDER BY MAX(lvl) DESC, COUNT(*) DESC;
    

    或者相反,从孩子那里播种。
    (如果 ID 是 PRIMARY KEY,那么这可能会更快,因为 ID 上的 JOIN)

    WITH RCTE AS
    (
       SELECT ID as baseID, 0 as lvl, ID, Parent_ID
       FROM documentdetails
       WHERE Parent_ID IS NOT NULL
    
       UNION ALL
    
       SELECT r.baseID, lvl + 1, t.ID, t.Parent_ID
       FROM RCTE r
       JOIN documentdetails t ON t.ID = r.Parent_ID
    )
    SELECT ID as rootID, MAX(lvl) as Depth
    FROM RCTE 
    WHERE Parent_ID IS NULL
    GROUP BY ID
    ORDER BY MAX(lvl) DESC, COUNT(*) DESC;
    

    【讨论】:

    • 感谢 LukStorms 的建议。不幸的是,基本假设是错误的,这意味着,我不能断言嵌套级别存在最大值,因此解决方案不能基于硬编码方法。
    • @FDavidov 那么对不起。不知道如何通过纯 SQL 做到这一点。但我添加了另一个查询,可用于断言表中嵌套的最大级别是多少。
    • 感谢 LukStorms 的努力。我认为有一种优雅而简单的方法可以保持所需的灵活性并且不使用动态 SQL(在我使用您的附加查询来确定嵌套深度之后就是这种情况)。如果我遇到这样的解决方案(优雅),请将其作为答案与评论一起发布在这里,以便您收到警报并查看我的结果。干杯!!!
    猜你喜欢
    • 2019-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多