1 Ken Sánchez Chief Executive Officer 267 Karen Berg Application Specialist 265 Ashvini Sharma Network Administrator 268 Ramesh Meyyappan Application Specialist 266 Peter Connelly Networ
Trang 1FIGURE 17-1
Jean Trenary and her crew are the able Information Service Department at AdventureWorks
1 Ken Sánchez Chief Executive Officer
267 Karen Berg Application Specialist 265
Ashvini Sharma Network Administrator
268 Ramesh Meyyappan Application Specialist 266
Peter Connelly Network Administrator
269 Dan Bacon Application Specialist 272 Janaina Bueno Application Specialist
264 Stephanie Conroy Network Manager
263 Jean Trenary Information Services Manager
270 François Ajenstat Database Administrator 271 Dan Wilson Database Administrator
Adventure Works 2008 Information Service Department
The next query result shows the data from the Information Service Department:
- - - -
-263 Jean Trenary Information Services Manager 1 Ken S´ anchez
Trang 2To examine one row from the employee perspective, Karen Berg’sBusinessEntityID(PK) is
264 HerManagerIDis 263 That’s theBusinessEntityIDfor Jean Trenary, so Karen reports
to Jean
From the supervisor’s point of view, hisBusinessEntityIDis stored in each of his direct report’s
rows in theManagerIDcolumn For example, Jean Trenary’sBusinessEntityIDis 263, so 263 is
stored in theManagerIDcolumn of everyone who reports directly to her
To maintain referential integrity, theManagerIDcolumn has a foreign key constraint that refers to
theBusinessEntityIDcolumn (the primary key) in the same table, as shown in Figure 17-2
TheManagerIDcolumn allows nulls so that the top person of the organization chart can report to
no one
FIGURE 17-2
The Employee table has a foreign key reference from the ManagerID column to the same table’s
primary key That’s why this pattern is often called theself-join pattern
Trang 3Restoring AdventureWorks2008’s Adjacency List
With the advent of the HierarchyID data type in SQL Server 2008, Microsoft removed the adjacency
list pattern from AdventureWorks2008 and replaced it with HierarchyID The following script
repairs AdventureWorks2008 and readies the database for experimentation with the adjacency list pattern:
ALTER TABLE HumanResources.Employee
ADD ManagerID INT
CONSTRAINT FK_AdjPairs
FOREIGN KEY(ManagerID)
REFERENCES HumanResources.Employee(BusinessEntityID);
GO
Update new ManagerID Column using join condition to match
UPDATE E
SET ManagerID = M.BusinessEntityID
FROM HumanResources.Employee AS E
JOIN HumanResources.Employee AS M
ON M.OrganizationNode = E.OrganizationNode.GetAncestor(1);
CREATE INDEX IxParentID
ON HumanResources.Employee
(ManagerID)
AdventureWorks2008is now ready for some adjacency list fun with Ken S´anchez, the CEO; Jean Trenary,
the IT Manager; and Franc¸ois Ajenstat, the DBA
Single-level queries
The simplest hierarchical task is to match every node with a direct connection one level up the
hierar-chy In this case, it means listing allAdventureWorks2008employees and their supervisor
The crux of the query is the self-join between two instances of theemployeetable represented
in theLEFT OUTER JOINbetween the employee’s (E)ManagerIDcolumn and the manager’s (M)
BusinessEntityIDcolumn There’s still one physicalemployeetable, but the query contains two
Trang 4Result (abbreviated):
Employee Manager
.
Of course, listing integers isn’t very useful The next query adds some meat to the bones and fleshes out
the data into a readable result set:
Employees and their Direct Supervisors
SELECT E.BusinessEntityID AS EmpID,
EP.FirstName + ‘ ‘ + EP.LastName AS EmpName, E.JobTitle AS EmpTitle,
E.ManagerID AS [MgrID],
MP.FirstName + ‘ ‘ + MP.LastName AS MgrName, M.JobTitle AS MgrTitle
FROM HumanResources.Employee AS E the employee
JOIN Person.Person AS EP the employee’s contact info
ON E.BusinessEntityID = EP.BusinessEntityID
LEFT OUTER JOIN HumanResources.Employee AS M the mgr(if there is one)
ON E.ManagerID = M.BusinessEntityID
LEFT JOIN Person.Person AS MP the manager’s contact info
ON M.BusinessEntityID = MP.BusinessEntityID
ORDER BY E.ManagerID , E.BusinessEntityID;
The abbreviated result is shown in Figure 17-3
FIGURE 17-3
Results from the query showing every employee and his manager
Trang 5The reverse of ‘‘find all managers,’’ which searches one level up the hierarchy query, is searching
down the hierarchy This query uses a downward looking join to locate every employee who has
direct reports The key to understanding this query is the self-join between theEmployeetable asM,
representing the managers, and theEmployeeasE, representing the employees:
Every Manager and their direct Reports SELECT M.BusinessEntityID AS ‘MgrID’,
MP.FirstName + ‘ ‘ + MP.LastName AS ‘MgrName’, M.JobTitle AS ‘MgrTitle’,
E.BusinessEntityID AS ‘EmpID’, EP.FirstName + ‘ ‘ + EP.LastName AS ‘EmpName’, E.JobTitle AS EmpTitle
FROM HumanResources.Employee AS M the manager JOIN Person.Person AS MP the manager’s contact info
ON M.BusinessEntityID = MP.BusinessEntityID JOIN HumanResources.Employee AS E the direct report
ON E.ManagerID = M.BusinessEntityID JOIN Person.Person AS EP the manager’s contact info
ON E.BusinessEntityID = EP.BusinessEntityID ORDER BY M.BusinessEntityID
The result is shown in Figure 17-4
FIGURE 17-4
Results of the query showing every manager and his direct reports
Trang 6WHERE ManagerID IS NOT NULL
GROUP BY ManagerID) AS C
ON M.BusinessEntityID = C.ManagerID;
ORDER BY Manager
Result (abbreviated):
- -
47 Andrew Hill Production Supervisor - WC10 7
192 Brenda Diaz Production Supervisor - WC40 12
273 Brian Welcker Vice President of Sales 3
228 Christian Kleinerman Maintenance Supervisor 4
.
Subtree queries
The preceding queries work well with one level If you need to return all the nodes for two levels,
then you could add another self-join As the number of levels needed increases, the queries become
complex and the query is fixed to a certain number of levels A continuing parade of self-joins is not
the answer
SQL queries should be able to handle any amount of data, so an adjacency list subtree query should be
able to handle any number of levels There are two solutions to the subtree problem when using the
adjacency model: a recursive CTE or a looping user-defined function
Recursive CTE down the hierarchy
The most direct solution to the subtree problem is the recursive common table expression, introduced
in SQL Server 2005 This variant of the CTE (the basic CTE is covered in Chapter 11, ‘‘Including Data
with Subqueries and CTEs’’) uses aUNION ALLand twoSELECTstatements
The firstSELECTstatement defines the anchor node from which the recursion will start, i.e., the top of
the subtree When the recursion starts, the row(s) returned by this query are added to the CTE
The secondSELECTdefines how rows are recursively added to the result set ThisSELECTstatement
joins with the CTE itself — similar to the earlier self-join — and uses theUNION ALLto add the rows
to the output of the CTE The key is that the secondSELECTwill continue to self-join through the
lay-ers of the hierarchy until no more rows are found
The loop stops executing when no more data is returned from the recursive step of the CTE The
first example focuses on the recursive nature of the CTE The firstSELECTin the CTE locates
the employee with aManagerIDof null, which should be the top of the hierarchy The name of
the CTE isOrgPath, so when the second select joins withOrgPathit can find all the related
nodes The CTE continues to execute the secondSELECTfor every level of the hierarchy until the
hierarchy ends:
Find all - Recursive CTE down the OrgChart
All employees who report to the CEO
simple query
Trang 7WITH OrgPath (BusinessEntityID, ManagerID, lv)
AS ( Anchor SELECT BusinessEntityID, ManagerID, 1 FROM HumanResources.Employee
WHERE ManagerID IS NULL should only be EmployeeID 1
Recursive Call
UNION ALL SELECT E.BusinessEntityID, E.ManagerID, lv + 1
FROM HumanResources.Employee AS E
JOIN OrgPath
ON E.ManagerID = OrgPath.BusinessEntityID
) SELECT BusinessEntityID, ManagerID, lv FROM OrgPath
ORDER BY Lv, BusinessEntityID OPTION (MAXRECURSION 20);
Result (abbreviated):
BusinessEntityID ManagerID lv -
.
Because you join to the CTE in the recursive section of the CTE, you can use columns from this
iteration to calculate the level in the hierarchy In the previous query, theLvcolumn is calculated
using thelvcolumn from the previous iteration+ 1 Thus, each iteration is given a sequential level
number
If the adjacency list data had a cyclic error (A reports to B, who reports to C, who reports to A), then
Trang 8The next query extends the previous recursive CTE by adding joins to flesh out the data It also uses a
different anchor node — Jean Trenary, Adventure Works’ IT Manager:
Find Subtree - Recursive CTE
All employees who report to IT Manager
full query with joins
WITH OrgPath (BusinessEntityID, ManagerID, lv)
AS (
Anchor
SELECT BusinessEntityID, ManagerID, 1
FROM HumanResources.Employee
WHERE BusinessEntityID = 263 Jean Trenary - IS Manager
Recursive Call
UNION ALL
SELECT E.BusinessEntityID, E.ManagerID, lv + 1
FROM HumanResources.Employee AS E
JOIN OrgPath
ON E.ManagerID = OrgPath.BusinessEntityID )
SELECT Lv, Emp.BusinessEntityID,
C.FirstName + ‘ ‘ + C.LastName AS [Name],
Emp.JobTitle,
OrgPath.ManagerID,
M.FirstName + ‘ ‘ + M.LastName AS [Manager]
FROM HumanResources.Employee AS Emp
JOIN OrgPath
ON Emp.BusinessEntityID = OrgPath.BusinessEntityID
JOIN Person.Person AS C
ON C.BusinessEntityID = Emp.BusinessEntityID
LEFT JOIN Person.Person AS M
ON Emp.ManagerID = M.BusinessEntityID
ORDER BY Lv, BusinessEntityID
OPTION (MAXRECURSION 20);
Result (abbreviated):
- - - - -
-1 263 Jean Trenary Information Services Manager 1 Ken S´ anchez
2 264 Stephanie Conroy Network Manager 263 Jean Trenary
2 267 Karen Berg Application Specialist 263 Jean Trenary
2 268 Ramesh Meyyappan Application Specialist 263 Jean Trenary
2 269 Dan Bacon Application Specialist 263 Jean Trenary
2 270 Fran¸ cois Ajenstat Database Administrator 263 Jean Trenary
2 271 Dan Wilson Database Administrator 263 Jean Trenary
2 272 Janaina Bueno Application Specialist 263 Jean Trenary
3 265 Ashvini Sharma Network Administrator 264 Stephanie Conroy
3 266 Peter Connelly Network Administrator 264 Stephanie Conroy
.
Trang 9User-defined function down the hierarchy
SQL Server 2005’s recursive CTE is not the only way to skin a hierarchy Before 2005, hierarchies were
handled with stored procedures or user-defined functions that looped through the hierarchy layers
adding each layer as a set to a temp table or table variable — effectively doing the same thing as a CTE
but with T-SQL
The following multiple-statement table-valued user-defined function accepts anEmployeeIDas a
parameter The function returns this employee and all the employees below them in the organization
tree The function definition includes the structure for the returned table variable, called@Tree The
function populates@Tree, first inserting the anchor node, the employee for theEmployeeIDpassed to
the function
This method jumps ahead a bit, using an advanced programming feature of SQL Server to solve the hierarchical problem For more information about one of my personal favorite features of SQL Server, see Chapter 25, ‘‘Building User-Defined Functions.’’
TheWHILEloop continues toINSERT INTO @treethe next hierarchy level until no more rows are
found (@@rowcount > 0)
Even though this is a loop, it’s still performing nice set-based inserts, one for every hierarchy level If
the hierarchy has a million nodes and is nine levels deep, then nine set-based inserts complete the entire
task from top to bottom:
User-Defined Functions for Navigating Adjacency list CREATE FUNCTION dbo.OrgTree
(@BusinessEntityID INT) RETURNS
@Tree TABLE (BusinessEntityID INT, ManagerID INT, Lv INT)
AS BEGIN DECLARE @LC INT = 1 insert the top level (anchor node)
INSERT @Tree (BusinessEntityID, ManagerID, Lv)
SELECT BusinessEntityID, ManagerID, @LC FROM HumanResources.Employee AS E the employee WHERE BusinessEntityID = @BusinessEntityID
Trang 10END RETURN
END; end of function
A table-valued function returns a result set so it’s called in theFROMclause The following query returns
AdventureWorks2008’s entire organizational chart:
test UDF
find all
simple query
SELECT * FROM dbo.OrgTree(1);
Result (abbreviated):
BusinessEntityID ManagerID Lv
-
.
Best Practice
The recursive CTE in this case is about twice as fast as the user-defined function However, the function
can include more variations of code, so it’s useful to know both methods
Just as with the recursive CTE, it’s easy to expand the query and include the data necessary for a
human-readable result set Just like the previous recursive CTE example, this query finds only the
subtree of those who work for Jean Trenary in the IT dept:
find subtree
all who work in IT
full query with joins
SELECT Lv, Emp.BusinessEntityID, Emp.JobTitle,
C.FirstName + ‘ ‘ + C.LastName AS [Name],
OrgTree.ManagerID,
M.FirstName + ‘ ‘ + M.LastName AS [Manager]
FROM HumanResources.Employee AS Emp
JOIN dbo.OrgTree (263) Jean Trenary, IT Manager
ON Emp.BusinessEntityID = OrgTree.BusinessEntityID JOIN Person.Person AS C