问题
I have an adjacency list in the DB and want to deliver the data in XML format to the client through a SQL SP. I'm trying to use CTE and FOR XML but I am not getting the XML nodes to nest.
FYI, this will represent a site map.
The Table structure:
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)
and the beginnings of the 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')
yields XML that looks like this:
<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>
when what I want is XML that looks like this:
<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>
I'm guessing the issue is not with the CTE but with the select, but I don't know where to start to fix it. Also, I don't know how deep the nesting will go, so I'm assuming I'll need it to support at least 10 levels deep.
Edit 1:
I think I'm getting closer... in looking at this page, I created a UDF but still there are some issues:
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
and the SQL that calls the UDF
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
this will nest the XML for me but it's duplicating nodes which is not what I want..
Edit 2:
I just needed to add a WHERE clause to the SELECT that calls the UDF:
...
WHERE ParentPageId IS NULL
回答1:
Turns out I didn't want the CTE at all, just a UDF that I call recursively
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
with the SQL that calls the UDF as
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
回答2:
The question as well as the OP's answer helped me a lot. It took me a bit to grasp the answer as I was missing some context. So here's a seperate answer with a more generic explanation (I've tried to remove every bit of code that didn't relate directly to getting hierarchical data in XML output).
Suppose the following typical table for hierarchical data:
CREATE TABLE Employee (Id INT, BossId INT, Name NVARCHAR(50));
Suppose it has the following data:
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');
To get hierarchical XML data we could use the following function:
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;
Which can be called like this:
SELECT dbo.fn_EmployeeHierarchyNode(1)
Or, if you want the root node as well, like this:
SELECT Id,
BossId,
Name,
dbo.fn_EmployeeHierarchyNode(Id)
FROM Employee
WHERE BossId IS NULL
FOR XML AUTO
Which would produce:
<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>
回答3:
Recursive CTEs are not recursive as in "nested", they work differently and what you're trying to do doesn't work with CTEs. (Think of them as being always tail-recursive.)
The only way I have found to build recursive XML in SQL Server is by creating a scalar function which renders the nodes recursively; functions can make recursive calls so that this works as expected.
来源:https://stackoverflow.com/questions/14765937/cte-and-for-xml-to-generate-nested-xml