Tree-like structures are common in our world: Company Hierarchy, File System on your computer, Product category map among others so you might run into a task of creating a Hierarchical level in your SQL query — In this blog post, I will show you how you can do that using couple of approaches. These approaches can also be used to map “parent – child” relationships.
Two approaches are:
- When you know the Tree Depth
- When you don’t know the Tree Depth
#1: When you know tree-depth:
When you know the tree-depth (and if it’s not too deep) then you could consider simple CTE’s to come up with the Hierarchical Levels field in your query.
Let’s take an example:
Input:
EmployeeID | FirstName | LastName | Title | ManagerID |
1 | Ken | Sánchez | Chief Executive Officer | NULL |
16 | David | Bradley | Marketing Manager | 273 |
273 | Brian | Welcker | Vice President of Sales | 1 |
274 | Stephen | Jiang | North American Sales Manager | 273 |
285 | Syed | Abbas | Pacific Sales Manager | 273 |
Query: (On SQL Server)
with lvl1 as
(select
[EmployeeID]
,[FirstName]
,[LastName]
,[Title]
,[ManagerID]
,1 as Level
FROM [dbo].[employees]
where ManagerID is null
)
,
lvl2 as
(
select
[EmployeeID]
,[FirstName]
,[LastName]
,[Title]
,[ManagerID]
,2 as Level
FROM [dbo].[employees]
where ManagerID IN (Select EmployeeID from lvl1)
),
lvl3 as
(
select
[EmployeeID]
,[FirstName]
,[LastName]
,[Title]
,[ManagerID]
,3 as Level
FROM [dbo].[employees]
where ManagerID IN (Select EmployeeID from lvl2)
)
select * from lvl1
union
select * from lvl2
union
select * from lvl3
Output:
EmployeeID | FirstName | LastName | Title | ManagerID | Level |
1 | Ken | Sánchez | Chief Executive Officer | NULL | 1 |
273 | Brian | Welcker | Vice President of Sales | 1 | 2 |
16 | David | Bradley | Marketing Manager | 273 | 3 |
274 | Stephen | Jiang | North American Sales Manager | 273 | 3 |
285 | Syed | Abbas | Pacific Sales Manager | 273 | 3 |
#2: When you do NOT know tree-depth:
In other words, if the tree is N-level deep then you are out of luck using option #1. In this case, you should consider the RECURSIVE CTE approach. Here’s an example:
Input: (with the idea that this table will grow over time)
EmployeeID | FirstName | LastName | Title | ManagerID |
1 | Ken | Sánchez | Chief Executive Officer | NULL |
16 | David | Bradley | Marketing Manager | 273 |
23 | Mary | Gibson | Marketing Specialist | 16 |
273 | Brian | Welcker | Vice President of Sales | 1 |
274 | Stephen | Jiang | North American Sales Manager | 273 |
275 | Michael | Blythe | Sales Representative | 274 |
276 | Linda | Mitchell | Sales Representative | 274 |
285 | Syed | Abbas | Pacific Sales Manager | 273 |
286 | Lynn | Tsoflias | Sales Representative | 285 |
Query: (On SQL Server that supports Recursive CTE)
with HierarchyLvl as
(
SELECT [EmployeeID]
,[FirstName]
,[LastName]
,[Title]
,[ManagerID]
,1 as Level
FROM [dbo].[employees]
where ManagerID is null
UNION ALL
SELECT e.[EmployeeID]
,e.[FirstName]
,e.[LastName]
,e.[Title]
,e.[ManagerID]
,Level + 1
FROM [dbo].[employees] e INNER JOIN HierarchyLvl d on e.ManagerID = d.EmployeeID
)
select * from HierarchyLvl
Output:
EmployeeID | FirstName | LastName | Title | ManagerID | Level |
1 | Ken | Sánchez | Chief Executive Officer | NULL | 1 |
273 | Brian | Welcker | Vice President of Sales | 1 | 2 |
16 | David | Bradley | Marketing Manager | 273 | 3 |
274 | Stephen | Jiang | North American Sales Manager | 273 | 3 |
285 | Syed | Abbas | Pacific Sales Manager | 273 | 3 |
286 | Lynn | Tsoflias | Sales Representative | 285 | 4 |
275 | Michael | Blythe | Sales Representative | 274 | 4 |
276 | Linda | Mitchell | Sales Representative | 274 | 4 |
23 | Mary | Gibson | Marketing Specialist | 16 | 4 |
Conclusion:
Even if I know tree-depth I will go with option #2 as it’s much easier to read and can accommodate future updates to the table. If you are interested in learning more about this and search for “Recursive Query using Common Table Expression” and you should find technical articles that talk about why it does what it does.
Hope this helps!