SQLServerCentral Article

Everybody Reports to Somebody

,

Everybody Reports To Somebody

Have you ever thought about the logistics of the organizational structure of a company? Do you

know how many layers of management exist from the top to the bottom? I was recently forced to consider

these very questions to support a new application that required managers to be able to access data

associated with any subordinate employee. This could be an employee that directly reports to the manager

or an employee that is several layers below the manager in the overall organizational structure.

For example, if my function received Bob's User ID, it should return the following people

based on the sample organizational chart (See Figure 1) . . .

  • Bob
  • Sue
  • Todd
  • Mary
  • Sandy
  • Megan
  • Randy


 Figure 1 

The table that stores our employee data (see figure 2) includes a 'User ID' field which

is the primary key for the table and a 'Manager User ID' field which contains the User ID of the manager

that each employee directly reports to (everybody reports to somebody philosophy). If Bob has a

User ID of '2' in our sample above, Sue, Todd, and Mary would all have a value of '2' in their

'Manager User ID' field indicating that they report directly to Bob (see figure 3).

Figure 2
 Figure 2 

Figure 3
 Figure 3 

Now for the tricky part . . . One of the objectives of our stored procedure is that it should not

assume any foreknowledge of the organizational structure including the number of management layers.

As I started this project I knew that my code would be required to perform some type of recursion,

so I found a great article on the Microsoft web site that discussed recursion in SQL. After spending a

little time reading the article, I discovered a couple of potential significant limitations in that SQL

recursion is limited to 32 loops (to prevent infinite loop conditions) and most of the examples for

passing data back up through the recursive loop chain used a data type that was restricted by a maximum

length. Although our company doesn't currently have 32 layers of management hierarchy, I simply didn't

want to settle for a 'work around' solution because I suspected it would eventually be an issue for us.

This caused me to start looking for alternatives which after much prayerful consideration yielded the

following stored procedure . . .

CREATE PROCEDURE select_subordinates_by_manager
     @UserID int 
As
Set NoCount On
Declare @UserCount int
Set @UserCount = 1
Create Table #In (UserID int) 
Create Table #Out (UserID int) 
Create Table #Result (UserID int) 
Insert Into #In (UserID)      Values (@UserID)
Insert Into #Result (UserID)      Values (@UserID)
While @UserCount > 0
    Begin
        Insert Into #Out (UserID)
        Select UserID From Test..Employee 
        Where ManagerUserID In (Select UserID From #In)
        Select @UserCount = (Select Count(UserID) From #Out)
        If @UserCount > 0
            Begin
                Insert Into #Result (UserID)
                Select UserID From #Out
            End
        Delete From #In
        Insert Into #In
        Select UserID From #Out
        Delete From #Out
    End
Set NoCount Off
Select UserID From #Result

The stored procedure basically starts off by returning a list of the User IDs that report directly to the specified manager User ID. It then runs in a loop checking for User IDs that report to the list of User IDs that were returned in the previous loop until no more User IDs are found. The User IDs returned from each successive loop are added to the temporary table called #Result which is ultimately used to return a list of User ID values representing the subordinate employees.

We typically use the User ID list returned from the previous stored procedure in conjunction with another stored procedure (see below) to return a list representing the employees that report to the specified manager either directly or indirectly.

CREATE PROCEDURE select_subordinate_names_by_manager 
    @UserID int
As
Create Table #Subordinates (UserID int)
Insert Into #Subordinates (UserID)
Exec ('dbo.select_subordinates_by_manager ' + @UserID)
Select e.UserID, e.FirstName, e.LastName 
  From Test..Employee as e
  Join #Subordinates as s on s.UserID = e.UserID
  Order By e.FirstName, e.LastName

After looking at the code and the extensive use of temporary tables you may be concerned about how well

this solution will perform. As a baseline, I measured the overall execution time of this technique in a

test environment (see environment details below) with a single organizational structure branch that

represents 50 layers of management and more than 4500 employees. Needless to say I was pleasantly

surprised to discover that the overall execution time was only 64 ms.

Test Environment

  • SQL Platform : Microsoft SQL Server 2000 (Developer Edition)
  • Hardware Platform : Workstation Class PC (Single Pentium 4 3.2Ghz/3GB RAM)

I hope you found this article informative and interesting . . .

Rate

3.25 (4)

You rated this post out of 5. Change rating

Share

Share

Rate

3.25 (4)

You rated this post out of 5. Change rating