Search code examples
sqlsql-serversql-server-2005treenested-sets

SQL Server Tree Hierarchy and Nested Sets with Duplicate Record ids


Given that I have this resultset structure (superfluous fields have been stripped)

Id | ParentId | Name | Depth
----------------------------

is it possible to have the records returned in tree order i.e. Parent then Children, if a Child is a Parent, then their Children, if not then Sibling, etc? For example,

Id | ParentId | Name | Depth
----------------------------
1    NULL       Major    1
2    1          Minor    2
3    1          Minor    2
4    3          Build    3
5    3          Build    3
6    1          Minor    2

/* etc, etc */

The only way that I can think of doing this would be to follow this article -

Improve hierarchy performance using nested sets

and include [LeftExtent] and [RightExtent] fields against each record. Now the SQL in the article works fine when Ids are unique, but in this particular tree structure, a record with the same Id can appear in different places within the tree (the ParentId field is different, obviously). I think the problem is in this SQL from the article -

  INSERT INTO @tmpStack
    (
      EmployeeID, 
      LeftExtent
    )
  SELECT TOP 1 EmployeeID, @counter 
  FROM Employee 
  WHERE ISNULL(ParentID, 0) = ISNULL(@parentid,0) 
  /* If the Id has already been added then record is not given [LeftExtent] or [RightExtent] values. */
  AND EmployeeID NOT IN (SELECT EmployeeID FROM @tmpStack) 

How can this be altered to allow records with duplicate Ids to be given [LeftExtent] and [RightExtent] values, or I am completely missing an easier way to return the resultset in the order that I require?


Solution

  • Here's one that does the trick for me:

    @ParentID is just a starting point in the hierarchy, but you can pass in 0 (but I think you're using null as the base ID, so you'll get the idea)

    The key to ordered sorting is with the sort key that's built up.

    WITH RoleHierarchy (RoleID, [Role], [Description], ParentID, Editable, HierarchyLevel, SortKey) AS
    (
       -- Base
       SELECT
            RoleID,
            [Role],
            [Description],
            ParentID,
            Editable,
            0 as HierarchyLevel,
            CAST(RoleID AS VARBINARY(300))
       FROM
            dbo.Roles       
       WHERE
            RoleID = @ParentID
    
       UNION ALL
    
       -- Recursive
       SELECT
            e.RoleID,
            e.[Role],
            e.[Description],
            e.ParentID,
            e.Editable,
            th.HierarchyLevel + 1 AS HierarchyLevel,
            CAST (th.SortKey + CAST (e.[Role] AS VARBINARY(100)) + CAST (e.[RoleID] AS VARBINARY(100)) AS VARBINARY(300))
       FROM
            Roles e
            INNER JOIN RoleHierarchy th ON e.ParentID = th.RoleID
        WHERE
            e.RoleID != 0
    )
    
    SELECT
        RoleID,
        ParentID,
        [Role],
        [Description],
        Editable,
        HierarchyLevel
    FROM
        RoleHierarchy
    WHERE
        RoleID != @ParentID
    ORDER BY
        SortKey