Let's say I've got the following table:
CREATE TABLE Employees
(
EmployeeId int PRIMARY KEY NOT NULL,
ParentEmployeId int REFERENCES Employees(EmployeeId) NULL,
Name varChar(255)
)
All records have a primary identifier and records are able to identify another record as a parent. (My actual schema isn't about employees, this is just a simplified version for illustration so if you've got a better way of handling employee information its not germane to this conversation.)
The following records are inserted:
INSERT INTO Employees VALUES (1, NULL, 'Company President 1')
INSERT INTO Employees VALUES (2, NULL, 'Company President 2')
INSERT INTO Employees VALUES (3, 1, 'Company President 1 - VP')
INSERT INTO Employees VALUES (4, 2, 'Company President 2 - VP')
INSERT INTO Employees VALUES (5, 3, 'Company President 1 - VP - Secretary')
INSERT INTO Employees VALUES (6, 4, 'Company President 2 - VP - Secretary')
INSERT INTO Employees VALUES (7, 5, 'Company President 1 - VP - Secretary - Sandwich Delivery')
These inserts represent:
Company President 1
Company President 1 - VP
Company President 1 - VP - Secretary
Company President 1 - VP - Secretary - Sandwich Delivery
Company President 2
Company President 2 - VP
Company President 2 - VP - Secretary
What I'm trying to do is for all employees that have a NULL ParentEmployeeId
I want to find the last person in the chain, which in this example would be "Company President 1 - VP - Secretary - Sandwich Delivery
" and "Company President 2 - VP - Secretary
".
I've got the following CTE which gives me everything including the nesting level but I'm not sure where to go from here. I'd like to avoid cursors if possible.
Also, and this is very important, I have logic elsewhere that guarantees that an employee can only have 1 direct subordinate. So although the schema technically allows for it, Company President 1
will never have two VP's listed.
WITH EmployeeRec(EmployeeId, ParentEmployeeId, Name, Level) AS
(
SELECT
EmployeeId,
ParentEmployeId,
Name,
1 as [Level]
FROM
Employees
WHERE
ParentEmployeId IS NULL
UNION ALL
SELECT
E.EmployeeId,
E.ParentEmployeId,
E.Name,
R.[Level] + 1
FROM
Employees E
INNER JOIN
EmployeeRec R
ON
E.ParentEmployeId = R.EmployeeId
)
SELECT * FROM EmployeeRec
A recursive CTE references itself. It returns the result subset, then it repeatedly (recursively) references itself, and stops when it returns all the results. FROM cte_name; Again, at the beginning of your CTE is the WITH clause.
A Recursive CTE is a CTE that references itself. The CTE repeatedly executes, returns subsets of data, until it returns the complete result set. Syntax. WITH cte_name AS ( cte_query_definition (or) initial query -- Anchor member UNION ALL recursive_query with condition -- Recursive member ) SELECT * FROM cte_name.
CTE was introduced in SQL Server 2005, the common table expression (CTE) is a temporary named result set that you can reference within a SELECT, INSERT, UPDATE, or DELETE statement. You can also use a CTE in a CREATE a view, as part of the view's SELECT query.
The common table expression (CTE) is a powerful construct in SQL that helps simplify a query. CTEs work as virtual tables (with records and columns), created during the execution of a query, used by the query, and eliminated after query execution.
Keeping track of your master EmployeeID allows you to join the results with the last level to retain the records you need.
WITH EmployeeRec(Master, EmployeeId, ParentEmployeeId, Name, Level) AS
(
SELECT
[Master] = EmployeeId,
EmployeeId,
ParentEmployeId,
Name,
1 as [Level]
FROM
Employees
WHERE
ParentEmployeId IS NULL
UNION ALL
SELECT
R.Master,
E.EmployeeId,
E.ParentEmployeId,
E.Name,
R.[Level] + 1
FROM
Employees E
INNER JOIN
EmployeeRec R
ON
E.ParentEmployeId = R.EmployeeId
)
SELECT *
FROM EmployeeRec er
INNER JOIN (
SELECT Master, Level = MAX(Level)
FROM EmployeeRec
GROUP BY Master
) m ON m.Master = er.Master
AND m.Level = er.Level
The key here is to keep track of the top-level parent in the recursive CTE:
;WITH EmployeeRec(
EmployeeId, ParentEmployeeId, UltimateGrandbossEmployeeId, Name, Level)
AS
(
SELECT
EmployeeId,
ParentEmployeeId,
EmployeeId UltimateGrandbossEmployeeId,
Name,
1 as [Level]
FROM
Employees
WHERE
ParentEmployeeId IS NULL
UNION ALL
SELECT
E.EmployeeId,
E.ParentEmployeeId,
R.UltimateGrandbossEmployeeId,
E.Name,
R.[Level] + 1
FROM
Employees E
INNER JOIN
EmployeeRec R
ON
E.ParentEmployeeId = R.EmployeeId
)
... form an intermediate CTE to capture the 'bottom up' level ...
SELECT
UltimateGrandbossEmployeeId,
Name,
ROW_NUMBER() OVER (PARTITION BY UltimateGrandbossEmployeeId
ORDER BY Level Desc ) BottomUp
FROM EmployeeRec
)
... for each ultimate grandboss, select their deepest child:
SELECT
UltimateGrandbossEmployeeId, DeepestChildName
FROM
Inter
WHERE
BottomUp = 1
(concatenate together all these code fragments to form a single query with two CTEs and a SELECT
)
Results:
1 Company President 1 - VP - Secretary - Sandwich Delivery
2 Company President 2 - VP - Secretary
You can JOIN
this back to Employee
to get ultiamte grandboss names or track the names in the CTE, as makes sense in your actual situation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With