
Query Transforms for hierarchyid
To maximize performance of querying hierarchies, SQL Server automatically performs three transformations of queries involving hierarchyid. The result of these transforms can be seen in the showplan output for transformed queries.
IsDescendantOf is transformed to an range seek
Given E as a column or a variable, E.IsDescendantOf(c) is transformed into a range seek. This significantly reduces the cost of finding descendants. If there is a depth-first index on c, this transformation helps because all descendants of E are co-located. For example, the code snippet @value.IsDescendantOf(EmployeeId) is executed as EmployeeId >= @Value AND EmployeeId <= @Value.DescendantLimit(). DescendantLimit is an internal method that determines the least upper bound of all possible descendants of a node. Notice that @value does not have to be a parameter. It could be a column, perhaps from a join condition.
GetAncestor is transformed to a range scan and residual predicate
GetAncestor(n) gives the nth ancestor of a node. This is useful when the precise relationship (parent, child, grandparent, etc.) between two nodes is needed, in contrast to the more general IsDescendantOf.
For example, execute the following query to find all employees whose direct manager is @value:
SELECT * FROM Employees WHERE EmployeeId.GetAncestor(1) = @value
This is transformed into a range scan for descendants of @value, with the original predicate as a residual. The code is transformed into the following:
SELECT * FROM Employees
WHERE
EmployeeId >= @Value AND EmployeeId <= @value.DescendantLimit()
AND EmployeeId.GetAncestor(1) = @value
The effect of this is to limit the scan to the subtree of @value.
GetAncestor is transformed to an index lookup using the breadth-first-index
In the above query, if @value is in the upper levels of the tree, the optimization above does not significantly reduce the number of rows scanned. When questions about nth ancestor are common, applications should create a breadth-first-index as previously described.
When a breadth-first index is present, the above query is further transformed to the following:
SELECT * FROM Employees
WHERE
EmployeeId >=@value AND EmployeeId <= @Value.DescendantLimit()
AND @value.GetLevel()+1 = EmployeeId.GetLevel()
The last line (containing the GetLevel methods) becomes an index lookup in the breadth-first-index. If EmployeeId is the clustering key, then it is the second column in the breadth-first index, and the two predicates become an index lookup that specifies exactly the n co-located direct reports of @value.
The GetAncestor transforms are not limited to queries for direct parents. The argument to GetAncestor can be any variable or constant.