When you have to save hierarchical data inside a database you can traverse the tree with recursion and for each note call the database to save it. But that is not the most elegant solution; because your tree might be quite big and you can end up with lots of calls to the database. You may also need to wrap this whole thing inside a transaction but as we all know stretching a transaction between several calls is not the best approach.

What we actually want is to transfer the hierarchical tree inside a single stored procedure and process it there. But how can you pass a hierarchical data to a single stored procedure? Well the answer as often is XML. Let’s assume you have this hierarchical XML:

DECLARE @xml XML

SET @xml = '
<Root>
    <Folder Name="/">
        <Folder Name="One">
            <Folder Name="One_One" />
        </Folder>
        <Folder Name="Two">
            <Folder Name="Two_One">
                <Folder Name="Two_One_One">
                    <Folder Name="Two_One_One_One" />
                </Folder>
                <Folder Name="Two_One_Two" />
            </Folder>
        </Folder>
    </Folder>
</Root>';

Then in order to traverse it we have to combine common table expressions with CROSS APPLY call to the current child node. This may look as follow:

WITH CTE_ITEMS (Folders, [Name], [Path], Level)
AS
(
    SELECT 
        VIRT.node.query('./*') as [Folders],
        VIRT.node.value('@Name', 'nvarchar(500)') as [Name],
        CAST('/' as nvarchar(max)) as [Path],
        0 as [Level]
        FROM @xml.nodes('/Root/Folder') as VIRT(node)
    UNION ALL
    SELECT 
        VIRT.node.query('./*') as [Folders], 
        VIRT.node.value('@Name', 'nvarchar(500)') as [Name],
        [Path] + 
            CASE [Path] WHEN '/' THEN '' ELSE + '/' END + 
            VIRT.node.value('@Name', 'nvarchar(max)') as [Path],
        Level + 1 as [Level]
        FROM 
    CTE_ITEMS CROSS APPLY Folders.nodes('./Folder') as VIRT(node)
)
SELECT [Name],[Path],[Level] FROM CTE_ITEMS ORDER BY [Path]

And finally the result gives us all we need to traverse the tree:

Name Path Level
/ / 0
One /One 1
One_One /One/One_One 2
Two /Two 1
Two_One /Two/Two_One 2
Two_One_One /Two/Two_One/Two_One_One 3
Two_One_One_One /Two/Two_One/Two_One_One/Two_One_One_One 4
Two_One_Two /Two/Two_One/Two_One_Two 3

Feedback

Posted on 6/6/2008 1:57:09 PM

Is there a way to see the Parent Name in the list?

Posted on 6/6/2008 2:05:45 PM

with the current query it won't be accessible with XPath because the recursive call does not include the parent node but you can get it from the path as:

WITH CTE_ITEMS ([Parent], Folders, [Name], [Path], Level)   
AS   
(   
    SELECT    
        CAST(NULL as nvarchar(max)) as [ParentName],   
        VIRT.node.query('./*') as [Folders],   
        VIRT.node.value('@Name', 'nvarchar(500)') as [Name],   
        CAST('/' as nvarchar(max)) as [Path],   
        0 as [Level]   
        FROM @xml.nodes('/Root/Folder') as VIRT(node)   
    UNION ALL   
    SELECT   
        REVERSE(SUBSTRING( REVERSE([Path]) ,0 , CHARINDEX( '/', REVERSE([Path])) + 1 )) as [ParentName],    
        VIRT.node.query('./*') as [Folders],    
        VIRT.node.value('@Name', 'nvarchar(500)') as [Name],   
        [Path] +    
            CASE [Path] WHEN '/' THEN '' ELSE + '/' END +    
            VIRT.node.value('@Name', 'nvarchar(max)') as [Path],   
        Level + 1 as [Level]   
        FROM    
    CTE_ITEMS CROSS APPLY Folders.nodes('./Folder') as VIRT(node)   
)   
SELECT [Parent],[Name],[Path],[Level] FROM CTE_ITEMS ORDER BY [Path]   
Posted on 11/28/2008 12:08:36 AM

It was really a nice one. But, here in my case I need an xml to be generated from a table which had parent/child relation at multiple levels. That means exactly opposite to the example you have given.will it be possible to do in T-SQL.

Please post your comments:

Name:  
Email (optional): Your email address will not be posted.
URL (optional):
Comments: HTML will be ignored, URLs will be converted to hyperlinks
Use [code] if(true) alert("OK"); [/code] for source code
 
Enter the text you see in the box: