With a hierarchical table populated and indexed, you’re
ready to start writing efficient queries against it using some more
methods available on the hierarchyid type.
The IsDescendantOf Method
The IsDescendantOf method is invoked on one hierarchyid value and accepts another hierarchyid value as its parameter. It returns a bit (Boolean) value of 1 (true) if the hierarchyid that the method is invoked on is a descendant (either directly or indirectly) of the hierarchyid
that is passed in as the parameter. Thus, this method essentially
returns a subtree whose root is the node specified by the parameter.
Because of its vertical traversal, it delivers very fast performance
for tables that have a depth-first index.
You can easily query the employee table to return all the descendants (child rows, grandchild rows, and so on) of a particular employee using the IsDescendantOf method. For example, the following query lists all of Amy’s descendants.
DECLARE @AmyNodeId hierarchyid
SELECT @AmyNodeId = NodeId
FROM Employee
WHERE EmployeeId = 46
SELECT NodeId.ToString() AS NodeIdPath, *
FROM Employee
WHERE NodeId.IsDescendantOf(@AmyNodeId) = 1
ORDER BY NodeLevel, NodeId
This query selects all rows whose NodeId values are descendants of Amy (employee ID 46). Here is the result:
NodeIdPath NodeId NodeLevel EmployeeId EmployeeName Title
---------- ------ --------- ---------- ------------ ----------------------
/1/ 0x58 1 46 Amy Marketing Specialist
/1/1/ 0x5AC0 2 269 Cheryl Marketing Assistant
/1/2/ 0x5B40 2 389 Wanda Business Assistant
/1/1/1/ 0x5AD6 3 87 Richard Business Intern
/1/1/2/ 0x5ADA 3 90 Jeff Business Intern
(5 row(s) affected)
Notice
that the result includes Amy in addition to all of her descendants. Amy
is returned because she is considered to be her “own descendant” at the
0 level of the subtree. Thus, you have selected a subtree that includes
all of Amy’s descendants, no matter how many levels deep they might
exist beneath her, with Amy at the root.
To select only Amy’s immediate child rows that are just one level beneath her, you can use the GetAncestor method. (You used this method earlier to create the uspAddEmployee stored procedure.) For example:
SELECT NodeId.ToString() AS NodeIdPath, *
FROM Employee
WHERE NodeId.GetAncestor(1)
=
(SELECT NodeId
FROM Employee
WHERE EmployeeId = 46)
ORDER BY NodeLevel, NodeId
GO
NodeIdPath NodeId NodeLevel EmployeeId EmployeeName Title
---------- ------ --------- ---------- ------------ ----------------------
/1/1/ 0x5AC0 2 269 Cheryl Marketing Assistant
/1/2/ 0x5B40 2 389 Wanda Business Assistant
(2 row(s) affected)
This
time, the results list only Cheryl and Wanda, but not Amy nor any of
Amy’s deeper descendants. That’s because you are requesting just the
rows whose one-level-up ancestor is Amy (employee ID 46)—that is, just
Amy’s immediate children.
If you wanted to retrieve all the employees exactly two levels down from a particular manager, you could pass the value 2 to the GetAncestor method.
For example, to select the employees that report to the employees
beneath Dave (that is, to see all the employees two levels beneath
him), you could request rows whose grandparent is Dave (employee ID 6; that is, just Dave’s grandchildren), as shown here:
SELECT NodeId.ToString() AS NodeIdPath, *
FROM Employee
WHERE NodeId.GetAncestor(2)
=
(SELECT NodeId
FROM Employee
WHERE EmployeeId = 6)
ORDER BY NodeLevel, NodeId
GO
NodeIdPath NodeId NodeLevel EmployeeId EmployeeName Title
---------- ------ --------- ---------- ------------ ----------------------
/1/1/ 0x5AC0 2 269 Cheryl Marketing Assistant
/1/2/ 0x5B40 2 389 Wanda Business Assistant
/2/1/ 0x6AC0 2 272 Mary Marketing Assistant
/3/1/ 0x7AC0 2 291 Keven Marketing Intern
(4 row(s) affected)
This
query returned all employees that are two levels below Dave. The fact
that some of them are cousins and not siblings (that is, some of them
have different direct parents) is irrelevant. Being exactly two levels
down beneath Dave qualifies them all for selection by this query. And
because of its horizontal traversal, it delivers very fast performance
with a breadth-first index defined on the table.
To find the root node in the hierarchy, simply invoke the GetRoot method on the hierarchyid
data type itself using the double-colon syntax, as shown in the
following code. This is the same method you used to create the first
employee at the top of the tree.
SELECT NodeId.ToString() AS NodeIdPath, *
FROM Employee
WHERE NodeId = hierarchyid::GetRoot()
GO
NodeIdPath NodeId NodeLevel EmployeeId EmployeeName Title
---------- ------ --------- ---------- ------------ ----------------------
/ 0x 0 6 Dave Marketing Manager
(1 row(s) affected)