Question

Say, I have an organizational structure that is 5 levels deep:

CEO -> DeptHead -> Supervisor -> Foreman -> Worker

The hierarchy is stored in a table Position like this:

PositionId | PositionCode | ManagerId
         1 |          CEO |      NULL
         2 |       DEPT01 |         1
         3 |       DEPT02 |         1
         4 |       SPRV01 |         2
         5 |       SPRV02 |         2
         6 |       SPRV03 |         3
         7 |       SPRV04 |         3
       ... |          ... |       ...

PositionId is uniqueidentifier. ManagerId is the ID of employee's manager, referring PositionId from the same table.

I need a SQL query to get the hierarchy tree going down from a position, provided as parameter, including the position itself. I managed to develop this:

-- Select the original position itself
SELECT
'Rank' = 0,
Position.PositionCode
FROM Position
WHERE Position.PositionCode = 'CEO' -- Parameter
-- Select the subordinates
UNION
SELECT DISTINCT
'Rank' =
    CASE WHEN Pos2.PositionCode IS NULL THEN 0 ELSE 1+
        CASE WHEN Pos3.PositionCode IS NULL THEN 0 ELSE 1+
            CASE WHEN Pos4.PositionCode IS NULL THEN 0 ELSE 1+
                CASE WHEN Pos5.PositionCode IS NULL THEN 0 ELSE 1
                END
            END
        END
    END,
'PositionCode' = RTRIM(ISNULL(Pos5.PositionCode, ISNULL(Pos4.PositionCode, ISNULL(Pos3.PositionCode, Pos2.PositionCode)))),
FROM Position Pos1
LEFT JOIN Position Pos2
ON Pos1.PositionId = Pos2.ManagerId
LEFT JOIN Position Pos3
ON Pos2.PositionId = Pos3.ManagerId
LEFT JOIN Position Pos4
ON Pos3.PositionId = Pos4.ManagerId
LEFT JOIN Position Pos5
ON Pos4.PositionId = Pos5.ManagerId
WHERE Pos1.PositionCode = 'CEO' -- Parameter
ORDER BY Rank ASC

It works not only for 'CEO' but for any position, displaying its subordinates. Which gives me the following output:

Rank | PositionCode
   0 |          CEO
 ... |          ...
   2 |       SPRV55
   2 |       SPRV68
 ... |          ...
   3 |       FRMN10
   3 |       FRMN12
 ... |          ...
   4 |       WRKR01
   4 |       WRKR02
   4 |       WRKR03
   4 |       WRKR04

My problems are:

The output does not include intermediate nodes - it will only output end nodes, i.e. workers and intermediate managers which have no subordinates. I need all intermediate managers as well.

I have to manually UNION the row with original position on top of the output. I there any more elegant way to do this?

I want the output to be sorted in hieararchical tree order. Not all DeptHeads, then all Supervisors, then all Foremen then all workers, but like this:

Rank | PositionCode
   0 |          CEO
   1 |       DEPT01
   2 |       SPRV01
   3 |       FRMN01
   4 |       WRKR01
   4 |       WRKR02
 ... |          ...
   3 |       FRMN02
   4 |       WRKR03
   4 |       WRKR04
 ... |          ...

Any help would be greatly appreciated.

Was it helpful?

Solution

Try a recursive CTE, the example on TechNet is almost identical to your problem I believe:

http://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx

OTHER TIPS

Thx, everyone suggesting CTE. I got the following code and it's working okay:

WITH HierarchyTree (PositionId, PositionCode, Rank)
AS
(
-- Anchor member definition
    SELECT PositionId, PositionCode, 
        0 AS Rank
    FROM Position AS e
    WHERE PositionCode = 'CEO'
    UNION ALL
-- Recursive member definition
    SELECT e.PositionId, e.PositionCode, 
        Rank + 1
    FROM Position AS e
    INNER JOIN HierarchyTree AS d
        ON e.ManagerId = d.PositionId
)
SELECT Rank, PositionCode
FROM HierarchyTree
GO

I had a similar problem to yours on a recent project but with a variable recursion length - typically between 1 and 10 levels.

I wanted to simplify the SQL side of things so I put some extra work into the logic of storing the recursive elements by storing a "hierarchical path" in addition to the direct manager Id.

So a very contrived example:

Employee

Id |  JobDescription   | Hierarchy | ManagerId
1  |  DIRECTOR         |   1\      |   NULL
2  |  MANAGER 1        |   1\2\    |   1
3  |  MANAGER 2        |   1\3\    |   1
4  |  SUPERVISOR 1     |   1\2\4   |   2
5  |  SUPERVISOR 2     |   1\3\5   |   3
6  |  EMPLOYEE 1       |   1\2\4\6 |   4
7  |  EMPLOYEE 2       |   1\3\5\7 |   5

This means you have the power to very quickly query any level of the tree and get all descendants by using a LIKE query on the Hierarchy column

For example

SELECT * FROM dbo.Employee WHERE Hierarchy LIKE '\1\2\%' 

would return

MANAGER 1
SUPERVISOR 1
EMPLOYEE 1

Additionally you can also easily get one level of the tree by using the ManagerId column.

The downside to this approach is you have to construct the hierarchy when inserting or updating records but believe me when I say this storage structure saved me a lot of pain later on without the need for unnecessary query complexity.

One thing to note is that my approach gives you the raw data - I then parse the result set into a recursive strongly typed structure in my services layer. As a rule I don't tend to format output in SQL.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top