CTE和FOR XML生成嵌套XML

Ed *_*nek 11 xml t-sql sql-server for-xml common-table-expression

我在数据库中有一个邻接列表,希望通过SQL SP将XML格式的数据传递给客户端.我正在尝试使用CTE和FOR XML,但我没有让XML节点嵌套.

仅供参考,这将代表一个站点地图.

表格结构:

CREATE TABLE [dbo].[PageHierarchy](
    [ModuleId] [int] NOT NULL,
    [PageId] [int] IDENTITY(1,1) NOT NULL,
    [ParentPageId] [int] NULL,
    [PageUrl] [nvarchar](100) NULL,
    [PageTitle] [nvarchar](50) NOT NULL,
    [PageOrder] [int] NULL)
Run Code Online (Sandbox Code Playgroud)

和CTE的开始:

;WITH cte AS
(
    select * from PageHierarchy where ParentPageId is null
    union all
    select child.* from PageHierarchy child inner join cte parent on parent.PageId = child.ParentPageId
)
SELECT ModuleId, PageId, ParentPageId, PageUrl, PageTitle, PageOrder FROM cte
group by ModuleId, PageId, ParentPageId, PageUrl, PageTitle, PageOrder
order by PageOrder
for xml auto, root ('bob')
Run Code Online (Sandbox Code Playgroud)

产生如下所示的XML:

<bob>
  <cte ModuleId="1" PageId="1" PageUrl="~/Admin/" PageTitle="Administration" PageOrder="1000" />
  <cte ModuleId="1" PageId="4" ParentPageId="1" PageTitle="Manage Users" PageOrder="1030" />
  <cte ModuleId="1" PageId="5" ParentPageId="4" PageUrl="~/Admin/AddUser" PageTitle="Add Users" PageOrder="1040" />
  <cte ModuleId="1" PageId="8" ParentPageId="4" PageUrl="~/Admin/EditUser" PageTitle="Edit/Search User" PageOrder="1070" />
</bob>
Run Code Online (Sandbox Code Playgroud)

当我想要的是XML,看起来像这样:

<bob>
  <cte ModuleId="1" PageId="1" PageUrl="~/Admin/" PageTitle="Administration" PageOrder="1000" />
  <cte ModuleId="1" PageId="4" ParentPageId="1" PageTitle="Manage Users" PageOrder="1030" >
    <cte ModuleId="1" PageId="5" ParentPageId="4" PageUrl="~/Admin/AddUser" PageTitle="Add Users" PageOrder="1040" />
    <cte ModuleId="1" PageId="8" ParentPageId="4" PageUrl="~/Admin/EditUser" PageTitle="Edit/Search User" PageOrder="1070" />
  </cte>
</bob>
Run Code Online (Sandbox Code Playgroud)

我猜这个问题不是CTE,而是选择,但我不知道从哪里开始修复它.此外,我不知道嵌套会有多深,所以我假设我需要它来支持至少10级深度.

编辑1:
我想我越来越近......在查看这个页面时,我创建了一个UDF,但仍有一些问题:

CREATE FUNCTION PageHierarchyNode(@PageId int)
RETURNS XML
WITH RETURNS NULL ON NULL INPUT 
BEGIN RETURN 
  (SELECT ModuleId AS "@ModuleId", PageId AS "@PageId",
    ParentPageId AS "@ParentPageId", PageUrl AS "@PageUrl",
    PageTitle AS "@PageTitle", PageOrder AS "@PageOrder", 
      CASE WHEN ParentPageId=@PageId
      THEN dbo.PageHierarchyNode(PageId)
      END
   FROM dbo.PageHierarchy WHERE ParentPageId=@PageId
   FOR XML PATH('Page'), TYPE)
END
Run Code Online (Sandbox Code Playgroud)

以及调用UDF的SQL

SELECT ModuleId AS "@ModuleId", PageId AS "@PageId",
    ParentPageId AS "@ParentPageId", PageUrl AS "@PageUrl",
    PageTitle AS "@PageTitle", PageOrder AS "@PageOrder", 
    dbo.PageHierarchyNode(PageId)
FROM PageHierarchy
FOR XML PATH('Page'), ROOT('SiteMap'), TYPE
Run Code Online (Sandbox Code Playgroud)

这将为我嵌套XML,但它是复制节点,这不是我想要的..

编辑2:

我只需要向调用UDF的SELECT添加一个WHERE子句:

...
WHERE ParentPageId IS NULL
Run Code Online (Sandbox Code Playgroud)

Ed *_*nek 12

结果我完全不想要CTE,只是一个我递归调用的UDF

CREATE FUNCTION PageHierarchyNode(@PageId int)
RETURNS XML
WITH RETURNS NULL ON NULL INPUT 
BEGIN RETURN 
  (SELECT ModuleId AS "@ModuleId", PageId AS "@PageId",
    ParentPageId AS "@ParentPageId", PageUrl AS "@PageUrl",
    PageTitle AS "@PageTitle", PageOrder AS "@PageOrder", 
      CASE WHEN ParentPageId=@PageId
      THEN dbo.PageHierarchyNode(PageId)
      END
   FROM dbo.PageHierarchy WHERE ParentPageId=@PageId
   FOR XML PATH('Page'), TYPE)
END
Run Code Online (Sandbox Code Playgroud)

使用调用UDF的SQL

SELECT ModuleId AS "@ModuleId", PageId AS "@PageId",
    ParentPageId AS "@ParentPageId", PageUrl AS "@PageUrl",
    PageTitle AS "@PageTitle", PageOrder AS "@PageOrder", 
    dbo.PageHierarchyNode(PageId)
FROM PageHierarchy
WHERE ParentPageId IS NULL
FOR XML PATH('Page'), ROOT('SiteMap'), TYPE
Run Code Online (Sandbox Code Playgroud)


Jer*_*oen 11

这个问题以及OP的答案对我帮助很大.由于我遗漏了一些背景,我花了一些时间来理解答案.所以这里是一个单独的答案,有一个更通用的解释(我试图删除与XML输出中获取分层数据无直接关系的所有代码).


假设以下典型的分层数据表:

CREATE TABLE Employee (Id INT, BossId INT, Name NVARCHAR(50));
Run Code Online (Sandbox Code Playgroud)

假设它有以下数据:

INSERT INTO Employee (Id, BossId, Name) VALUES
(1, NULL, 'Boss Pancone'),
(2, 1, 'Capioregime Luciano'),
(3, 1, 'Capioregime Bruno'),
(4, 2, 'Johnny'),
(5, 2, 'Luca'),
(6, 2, 'Luciano jr.'),
(7, 3, 'Marco'),
(8, 3, 'Mario'),
(9, 3, 'Giacomo');
Run Code Online (Sandbox Code Playgroud)

要获取分层XML数据,我们可以使用以下函数:

ALTER FUNCTION dbo.fn_EmployeeHierarchyNode (@BossId INT) RETURNS XML
BEGIN RETURN
    (SELECT Id, 
            BossId, 
            Name,
            dbo.fn_EmployeeHierarchyNode(Id)
        FROM Employee
        WHERE BossId = @BossId
        FOR XML AUTO)
END;
Run Code Online (Sandbox Code Playgroud)

可以这样调用:

SELECT dbo.fn_EmployeeHierarchyNode(1)
Run Code Online (Sandbox Code Playgroud)

或者,如果您也想要根节点,如下所示:

SELECT  Id,
        BossId,
        Name,
        dbo.fn_EmployeeHierarchyNode(Id)
FROM    Employee
WHERE   BossId IS NULL
FOR     XML AUTO
Run Code Online (Sandbox Code Playgroud)

哪会产生:

<Employee Id="1" Name="Boss Pancone">
  <Employee Id="2" BossId="1" Name="Capioregime Luciano">
    <Employee Id="4" BossId="2" Name="Johnny" />
    <Employee Id="5" BossId="2" Name="Luca" />
    <Employee Id="6" BossId="2" Name="Luciano jr." />
  </Employee>
  <Employee Id="3" BossId="1" Name="Capioregime Bruno">
    <Employee Id="7" BossId="3" Name="Marco" />
    <Employee Id="8" BossId="3" Name="Mario" />
    <Employee Id="9" BossId="3" Name="Giacomo" />
  </Employee>
</Employee>
Run Code Online (Sandbox Code Playgroud)


Luc*_*ero 6

递归CTE不像"嵌套"那样递归,它们的工作方式不同,而您尝试做的事情不适用于CTE.(把它们想象成总是尾递归的.)

我发现在SQL Server中构建递归XML的唯一方法是创建一个以递归方式呈现节点的标量函数; 函数可以进行递归调用,以便按预期工作.