11

我在数据库中有一个邻接列表,并希望通过 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)

以及 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')

生成如下所示的 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>

当我想要的是看起来像这样的 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>

我猜问题不在于 CTE,而在于 select,但我不知道从哪里开始修复它。另外,我不知道嵌套有多深,所以我假设我需要它来支持至少 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

以及调用 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

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

编辑2:

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

...
WHERE ParentPageId IS NULL
4

3 回答 3

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

使用将 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
于 2013-02-08T18:04:52.303 回答
11

这个问题以及OP的回答对我帮助很大。由于我缺少一些上下文,我花了一点时间来掌握答案。所以这里有一个单独的答案和更通用的解释(我试图删除与在 XML 输出中获取分层数据没有直接关系的每一段代码)。


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

CREATE TABLE Employee (Id INT, BossId INT, Name NVARCHAR(50));

假设它有以下数据:

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');

要获取分层 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;

可以这样调用:

SELECT dbo.fn_EmployeeHierarchyNode(1)

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

SELECT  Id,
        BossId,
        Name,
        dbo.fn_EmployeeHierarchyNode(Id)
FROM    Employee
WHERE   BossId IS NULL
FOR     XML AUTO

这会产生:

<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>
于 2013-07-10T13:26:04.167 回答
6

递归 CTE 不像“嵌套”那样递归,它们的工作方式不同,您尝试做的事情不适用于 CTE。(认为​​它们总是尾递归的。)

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

于 2013-02-08T04:59:42.293 回答