During my foray into XML SQL queries this week, I was presented with another challenge. Instead of getting just the immediate children of a category, I now needed to recursively select all children from a tree – to an unlimited depth.
A Common Table Expression (CTE, aka WITH statement) can also be called recursively, but requires UNION ALL to join the recursive and anchor members — and XML columns can’t be unioned.
Instead, we need a user defined function that returns XML TYPE. It’ll give us a rootless collection of products within a category, and call itself again to get sub-categories. Got it? Here’s the function definition, again using the AdventureWorks database:
CREATE FUNCTION GetProductCategoryChildren
(
@ParentProductCategoryID INT
)
RETURNS XML
AS
BEGIN
RETURN
(
SELECT
-- Map columns to XML attributes/elements with XPath selector
category.ProductCategoryID AS '@id',
category.Name AS '@name',
(
-- Use a sub query for child elements.
SELECT
ProductID AS '@id',
Name AS '@name',
ListPrice AS '@price'
FROM
SalesLT.Product
WHERE
ProductCategoryID = category.ProductCategoryID
FOR
XML PATH('product'), -- The element name for each row.
TYPE -- Column is typed so it nests as XML, not text.
) AS 'products',
dbo.GetProductCategoryChildren(category.ProductCategoryID)
AS 'categories' -- Recursive call to get child categories.
FROM
SalesLT.ProductCategory category
WHERE
category.ParentProductCategoryID = @ParentProductCategoryID
FOR
XML PATH('category'), -- The element name for each row.
TYPE -- The root element name for this result set.
)
END
This function works great. But we still want to get details about the group itself (not just its children), and we still need a root node so we can load it into an XmlDocument. Here’s how to wrap the call to this function to get a root node and details about the parent:
-- Get the parent group's name and child products.
SELECT
category.ProductCategoryID AS '@id',
category.Name AS '@name',
(
SELECT
ProductID AS '@id',
Name AS '@name',
ListPrice AS '@price'
FROM
SalesLT.Product
WHERE
ProductCategoryID = category.ProductCategoryID
FOR
XML PATH('product'), TYPE
) AS 'products',
-- start recursing to get child categories.
dbo.GetProductCategoryChildren(category.ProductCategoryID) AS 'categories'
FROM
SalesLT.ProductCategory category
WHERE
category.CategoryID = 2
FOR
XML PATH('category'), ROOT('categories')
This is what the output looks like. It’ll go for as many levels of depth as your tree does.
<categories>
<category id="2" name="Components">
<categories>
<category id="8" name="Handlebars">
<products>
<product id="808" name="LL Mountain Handlebars" price="44.5400" />
<product id="809" name="ML Mountain Handlebars" price="61.9200" />
<product id="810" name="HL Mountain Handlebars" price="120.2700" />
<product id="811" name="LL Road Handlebars" price="44.5400" />
<product id="812" name="ML Road Handlebars" price="61.9200" />
</products>
<categories>
<category id="9" name="Bottom Brackets">
<products>
<product id="994" name="LL Bottom Bracket" price="53.9900" />
<product id="995" name="ML Bottom Bracket" price="101.2400" />
<product id="996" name="HL Bottom Bracket" price="121.4900" />
</products>
<categories>
<category id="11" name="Chains">
<products>
<product id="952" name="Chain" price="20.2400" />
</products>
</category>
</categories>
</category>
<category id="10" name="Brakes">
<products>
<product id="907" name="Rear Brakes" price="106.5000" />
<product id="948" name="Front Brakes" price="106.5000" />
</products>
</category>
</categories>
</category>
<category id="12" name="Cranksets">
<products>
<product id="949" name="LL Crankset" price="175.4900" />
<product id="950" name="ML Crankset" price="256.4900" />
<product id="951" name="HL Crankset" price="404.9900" />
</products>
</category>
</categories>
</category>
</categories>
Note I had to rearrange some of the categories in the AdventureWorks database to get deeper nesting.
August 30, 2008



3 Comments
Steve K. on September 30, 2011 at 4:33 pm.
Thanks for this post. Though it is several years old, it helped me in my need for some menu building for a website I’m working on.
Dana on December 2, 2011 at 8:26 pm.
Thanks. This was one of the best explanations of xml I have seen and I’ve been looking around internet for a couple of months.
Rob on January 26, 2012 at 3:59 pm.
I agree. These are great examples. I needed to generate an XML file using some data in our database, to act as a configuration file for a SQLSpec – a great little documenting tool. This really helped, and saved me from having to deal with XSLT.