﻿<?xml version='1.0' encoding='UTF-8'?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>SQLServerCentral / Discuss Content Posted by Craig Hatley / Article Discussions / Article Discussions by Author  / Everybody Reports to Somebody / Latest Posts</title><generator>InstantForum.NET v2.9.0</generator><description>SQLServerCentral</description><link>http://www.sqlservercentral.com/Forums/</link><webMaster>notifications@sqlservercentral.com</webMaster><lastBuildDate>Sat, 25 May 2013 14:26:03 GMT</lastBuildDate><ttl>20</ttl><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>[quote][b]James Raddock (5/16/2008)[/b][hr]...with a sproc to generate the nested set as hierarchical data is needed (it's a cheap operation).[/quote]I know this is an old thread, but can you post the code for that sproc?  Thanks.</description><pubDate>Sun, 08 Aug 2010 20:24:53 GMT</pubDate><dc:creator>Jeff Moden</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>Being familiar with both the nested set model [specifically Celko's push stack] and the CTE model, they each have their advantages and are relatively easy to set up. I recommend the adjacency list for OLTP systems with a sproc to generate the nested set as hierarchical data is needed (it's a cheap operation).Another option that I feel should at least be mentioned is denormalizing your data, although it limits the number of levels you can have in your hierarchy. Denormalization, however, would be the fastest hybrid OLTP/OLAP solution (nested sets IMO are functional OLAP).- James</description><pubDate>Fri, 16 May 2008 12:13:46 GMT</pubDate><dc:creator>James Raddock</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>I like Craig's original approach because performance is good and it is straightforward.  Another important consideration is many, if not most organizations that I am aware of tend to follow the 'one-employee-one-manager' model.   I utilize essentially the same algorithm with three differences:  1) I encapsulate all of the code within the stored procedure, which simply outputs an ordered four-column dataset (SortName [Last, First], Employee ID, SupervisorID, Level), 2) I include the Level column (integer), which indicates how many levels down from the specified manager each employee is in the hierarchy, and 3) I have an additional input parameter for Level, which allows me to easily return only the direct reports for the manager (or some other number of levels deep that I may be interested in at the moment).  Of course, my method makes certain assumptions about how the names will be used since I am controlling the name format (we do a lot of dropdown lists and reports using the output.)</description><pubDate>Fri, 16 May 2008 08:13:22 GMT</pubDate><dc:creator>deanroush</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>"I was hoping it would appear in SS2005..."SQL Server 2005 introduced Common Table Expressions (CTE) which can be recursive.</description><pubDate>Mon, 04 Jun 2007 12:25:00 GMT</pubDate><dc:creator>Nils Gustav Stråbø</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Here's a short CTE, assuming a table named #Employees with the data shown in the article:&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;WITH OrgChart (UserID, LastName, FirstName, ManagerUserID)AS(    SELECT UserID, LastName, FirstName, ManagerUserID    FROM #Employees    WHERE ManagerUserID = 0    UNION ALL    SELECT e.UserID, e.LastName, e.FirstName, e.ManagerUserID    FROM #Employees e    INNER JOIN OrgChart o    ON e.ManagerUserID = o.UserID)SELECT UserID, LastName, FirstName, ManagerUserIDFROM OrgChart;&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face=Arial&gt;Please excuse any typos - I had to retype this in here.&lt;/FONT&gt;&lt;/P&gt;</description><pubDate>Mon, 04 Jun 2007 11:31:00 GMT</pubDate><dc:creator>Mike C</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;&lt;EM&gt;"In practice, does the data in the set correspond to an organisation's departmental structure, rather than individual relationships"&lt;/EM&gt;&lt;/P&gt;&lt;P&gt;This is just a different model for storing hierarchical data in SQL tables. The origial article presented an adjacent set model, Mike presented a nested sets model, and there's another I've seen used called the maternalized path model. The maternalized path model is basically the same structure as a URL, folders and paths. In that model you store a node's entire ancestry in a column. To get to your question though, with both the nested sets and maternalized path models the information you use to maintain the tree structure can be, and usually is, entirely fictional with no relationship whatsoever with the data it's associated with. Kind of like inventing an ID column for a table instead of using a person's name as a key for instance.&lt;/P&gt;&lt;P&gt;I haven't had to model hierarchical data for a project in a while but I used the nested sets model to do it when I did. It really isn't as difficult to use or maintain as some articles I've read about it indicate -- of course I'll suffix that by saying each model has its advantages and disadvantages. Changing someone's boss, for instance, is essentially moving a node within the tree. In the nested sets model this boils down to updating rows that are between numerical ranges and adding or subtracting numbers. With the maternalized path model you update rows that are LIKE a certain string. In either case you could potentially update every row in the table. The adjacent set model wouldn't update as many rows to do this.&lt;/P&gt;</description><pubDate>Mon, 04 Jun 2007 09:31:00 GMT</pubDate><dc:creator>Timothy-313907</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Hi David and Craig,&lt;/P&gt;&lt;P&gt;The LeftID and RightID define a subset.  In the case of John, his LeftID=1 and RightID=24.  Anyone with a LeftID and RightID between these two numbers are defined by a subset of John's set.  Bob's has the LeftID=2 and RightID=15, so his numbers are a subset of John's set {1..24} (apologies for the "fake" set notation &lt;img src='images/emotions/smile.gif' height='20' width='20' border='0' title='Smile' align='absmiddle'&gt; .)  Therefore, Bob reports to John.  And so on...&lt;/P&gt;&lt;P&gt;Actually you don't need to use the adjacency list to renumber the left and right ID's of your nested sets.  There are some other options to improve efficiency.  In the example I gave the sets were a "tight" fit, with the left and right id numbers as close together as possible.  However, that's not a requirement.  You can actually spread them out further to start with, so long as they still properly "nest" within one another.  For instance:&lt;/P&gt;&lt;P&gt;John Smith, LeftID=1, RightID=1000&lt;/P&gt;&lt;P&gt;Bob Johnson, LeftID=20, RightID=150&lt;/P&gt;&lt;P&gt;Sue Fields, LeftID=300, RightID=400&lt;/P&gt;&lt;P&gt;In this case, Bob and Sue are still nested within John Smith, but the range between LeftID and RightID has been widened.  You can easily insert or delete individuals without recalculating (dependent on business rules).  The recalculations come in when Bob's entire range is filled and he gets another subordinate.&lt;/P&gt;&lt;P&gt;Using this method you don't need to recalculate LeftID and RightID as often, which, as David pointed out can hurt performance on large data sets.  Renumbering can also be done with just a few statements (albeit slightly more complex ones).  This method also has the advantage that you can calculate things like the level of each employee in the hierarchy, total height of the tree, etc., relatively easily.&lt;/P&gt;&lt;P&gt;You can also convert adjacency list to nested sets, and vice versa.&lt;/P&gt;&lt;P&gt;Celko's written a lot on the topic of nested sets versus adjacency list.  He also has a pretty good book on the subject - "Trees and Hierarchies in SQL".  Although it's not SQL Server specific, his examples are fairly easy to port over.&lt;/P&gt;&lt;P&gt;Also, if you're going to use the adjacency list, you might consider using recursive CTEs (SQL 2K5) - they were defined for this type of thing &lt;img src='images/emotions/smile.gif' height='20' width='20' border='0' title='Smile' align='absmiddle'&gt;&lt;/P&gt;</description><pubDate>Mon, 04 Jun 2007 09:21:00 GMT</pubDate><dc:creator>Mike C</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Hi,&lt;/P&gt;&lt;P&gt;   I have received a private question asking about how to deal with employees that may report to multiple bosses and I wanted to provide my thoughts on the subject.  I think the best approach to address this issue would be to move the 'Manager User ID' field to a separate table containing at least the 'User ID' and 'Manager User ID' columns which could be joined to the 'Employee' table via the 'User ID' column.  You could then modify the stored procedure to include the new table via a join such as (Changes Highlighted In Yellow) . . .&lt;/P&gt;&lt;FONT color=#11bbbb&gt;CREATE PROCEDURE select_subordinates_by_manager     @UserID int AsSet NoCount OnDeclare @UserCount intSet @UserCount = 1Create 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 &amp;gt; 0    Begin        Insert Into #Out (UserID)        Select &lt;FONT style="BACKGROUND-COLOR: #ffff11"&gt;Distinct &lt;/FONT&gt;UserID From Test..Employee &lt;FONT style="BACKGROUND-COLOR: #ffff11"&gt;as e&lt;/FONT&gt;         &lt;FONT style="BACKGROUND-COLOR: #ffff11"&gt;Join Test..ManagedBy as m on m.UserID = e.UserID&lt;/FONT&gt;        Where &lt;FONT style="BACKGROUND-COLOR: #ffff11"&gt;m.&lt;/FONT&gt;ManagerUserID In (Select UserID From #In)        Select @UserCount = (Select Count(UserID) From #Out)        If @UserCount &amp;gt; 0            Begin                Insert Into #Result (UserID)                Select UserID From #Out            End        Delete From #In        Insert Into #In        Select UserID From #Out        Delete From #Out    EndSet NoCount OffSelect UserID From #Result&lt;/FONT&gt;</description><pubDate>Mon, 04 Jun 2007 09:04:00 GMT</pubDate><dc:creator>Craig Hatley</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Hi Mike,&lt;/P&gt;&lt;P&gt;found a further article at &lt;A href="http://www.intelligententerprise.com/001020/celko1_1.jhtml"&gt;http://www.intelligententerprise.com/001020/celko1_1.jhtml&lt;/A&gt;&lt;/P&gt;&lt;P&gt;It appears that if your organisation structure fequently changes, you would still need an adjacency list to calculate the nested sets though? Joe provides the code to convert one to the other. I guess the practical application of this solution  would be to write a trigger which recalculates the nested sets whenever the organisation structure is changed (i.e. you change an employee's manager id), storing the left and right values you calculate and using them as an aid to produce faster organisation structure reports.&lt;/P&gt;&lt;P&gt;If someone left the organisation, you would leave the existing calculated left and right values in place to provide a management hierarchy until a new manager was appointed.&lt;/P&gt;&lt;P&gt;PS: Have had several coffees now &lt;img src='images/emotions/blink.gif' height='20' width='20' border='0' title='Blink' align='absmiddle'&gt;&lt;/P&gt;&lt;P&gt;David&lt;/P&gt;</description><pubDate>Mon, 04 Jun 2007 08:14:00 GMT</pubDate><dc:creator>David le Quesne</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Hi Mike,&lt;/P&gt;&lt;P&gt;   Although your solution is a viable option (and we considered it), we chose to identify each employee's manager as a single field on each record because we wanted the ability to easily update this information to accomodate the incredible growth of our organization.  With the method we are using, we basically complete this single piece of information (manager's user ID) for each new employee during the account setup process and we are good to go.  &lt;img src='images/emotions/smile.gif' height='20' width='20' border='0' title='Smile' align='absmiddle'&gt;&lt;/P&gt;&lt;P&gt; &lt;/P&gt;</description><pubDate>Mon, 04 Jun 2007 07:52:00 GMT</pubDate><dc:creator>Craig Hatley</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Hi Tim,&lt;/P&gt;&lt;P&gt;Thanks for that link to Joe Celko's article, I had not come across this solution before. I can see that it is easier to model an org tree in SQL using set theory. In practice, does the data in the set correspond to an organisation's departmental structure, rather than individual relationships, otherwise you could get real problems if an employee switches managers and correspondingly needed to switch containments?&lt;/P&gt;&lt;P&gt;I like the idea that because the containment encapsulates all levels of the hierarchy, there is no break in the management chain if an employee in the middle-tier leaves, the model is effectively self-healing.&lt;/P&gt;&lt;P&gt;Presumably by creating overlapping sets you could also create organisation structures where individuals have multiple lines of management (many DBAs may be familiar with this &lt;img src='images/emotions/rolleyes.gif' height='20' width='20' border='0' title='Rolled Eyes' align='absmiddle'&gt;   )&lt;/P&gt;&lt;P&gt;David&lt;/P&gt;</description><pubDate>Mon, 04 Jun 2007 07:49:00 GMT</pubDate><dc:creator>David le Quesne</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>Mike's example is actually a nested set model, not an adjecent list model per the article. Basically instead of having a "boss" column in your employee table that's the same as an employee, each employee has containment information. You use two columns which represent the range of containment, a set, an employee has. Joe Celko explains it better, &lt;A href="http://www.developersdex.com/gurus/articles/112.asp"&gt;http://www.developersdex.com/gurus/articles/112.asp&lt;/A&gt;.</description><pubDate>Mon, 04 Jun 2007 07:18:00 GMT</pubDate><dc:creator>Timothy-313907</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>Quite liked that one, though as surprised as you at the efficiency.  Will give it a bash sometime.</description><pubDate>Mon, 04 Jun 2007 04:05:00 GMT</pubDate><dc:creator>CraigIW</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Hi Mike,&lt;/P&gt;&lt;P&gt;apologies for this, but i am interested in your right-brain set based solution, but I am feeling a bit left-brain this Monday morning, probably lack of coffee, so please could you set me right about what left-id and right-id represent because at the moment I'm feeling a bit left-out. &lt;img src='images/emotions/crazy.gif' height='20' width='20' border='0' title='Crazy' align='absmiddle'&gt;&lt;/P&gt;&lt;P&gt;Thanks&lt;/P&gt;&lt;P&gt;David&lt;/P&gt;</description><pubDate>Mon, 04 Jun 2007 03:49:00 GMT</pubDate><dc:creator>David le Quesne</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Oracle has "Connect by prior" &lt;/P&gt;&lt;P&gt;select * from person connect by prior id=supervisor_id&lt;/P&gt;&lt;P&gt;.. it makes traversing hierarchies trivial.&lt;/P&gt;&lt;P&gt;I was hoping it would appear in SS2005...  &lt;/P&gt;</description><pubDate>Mon, 04 Jun 2007 02:54:00 GMT</pubDate><dc:creator>Paul Nicklin</dc:creator></item><item><title>RE: Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>&lt;P&gt;Ahhh, ye olde adjacency list model.  You might get better efficiency from a set-based solution, like the nested sets model or one of its variants.  Here's an Q&amp;amp;D sample.&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;CREATE TABLE #Employees (ID INT PRIMARY KEY, LastName VARCHAR(30), FirstName VARCHAR(30), LeftID INT, RightID INT)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (1, 'Smith', 'John', 1, 24)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (2, 'Johnson', 'Bob', 2, 15)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (4, 'Fields', 'Sue', 3, 4)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (3, 'Burton', 'Debra', 16, 23)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (5, 'Jacobs', 'Todd', 11, 14)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (6, 'Sanders', 'Mary', 5, 10)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (10, 'Sims', 'Tom', 17, 18)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (11, 'Wright', 'Larry', 19, 20)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (12, 'Morgan', 'Betty', 21, 22)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (7, 'Teeter', 'Sandy', 12, 13)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (8, 'Morris', 'Megan', 6, 7)&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;INSERT INTO #Employees (ID, LastName, FirstName, LeftID, RightID)VALUES (9, 'Binks', 'Randy', 8, 9)GO&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;CREATE PROCEDURE dbo.GetOrgChart (@startID INT)ASBEGIN SELECT e1.ID, e1.LastName, e1.FirstName, e1.LeftID, e1.RightID, COUNT(e2.ID) AS Level FROM #Employees e1 INNER JOIN #Employees e2 ON e1.LeftID BETWEEN e2.LeftID AND e2.RightID WHERE e1.LeftID BETWEEN (SELECT LeftID FROM #Employees WHERE ID = @startid)  AND (SELECT RightID FROM #Employees WHERE ID = @startid) GROUP BY e1.ID, e1.LastName, e1.FirstName, e1.LeftID, e1.RightID ORDER BY e1.LeftIDENDGO&lt;/FONT&gt;&lt;/P&gt;&lt;P&gt;&lt;FONT face="Courier New"&gt;EXEC dbo.GetOrgChart 1&lt;/FONT&gt;&lt;/P&gt;</description><pubDate>Sun, 03 Jun 2007 21:36:00 GMT</pubDate><dc:creator>Mike C</dc:creator></item><item><title>Everybody Reports to Somebody</title><link>http://www.sqlservercentral.com/Forums/Topic366185-385-1.aspx</link><description>Comments posted here are about the content posted at &lt;A HREF="http://www.sqlservercentral.com/columnists/cHatley/3001.asp"&gt;http://www.sqlservercentral.com/columnists/cHatley/3001.asp&lt;/A&gt;</description><pubDate>Tue, 15 May 2007 22:30:00 GMT</pubDate><dc:creator>Craig Hatley</dc:creator></item></channel></rss>