Skip to content
Advertisement

Join repeatedly until a string is present?

While querying a subset of our employees, I’m trying to add a field for the SVP they “roll up” to.

Employees may have anywhere from 1 to 5 or 6 degrees of separation from their SVP. The trouble is, we don’t have any specific hierarchical indicator to reference. I have to do this by walking up through the employee’s manager repeatedly, until some manager’s manager has “SVP” in their title.

How could I write a query to do this?

From the opposite direction, I’ve found the employees of a specific SVP (named BM for the example) by saying, ‘Employee’s manager is BM, OR Employee’s manager’s manager is BM, OR Employee’s manager’s manager’s manager is BM’ and so on…

For my instance, I suspect I’d only use the same sys_user table over and over again, following the manager field each time until I reach a user with SVP in the title.

+--------+-------------------+-----------+--------+
| sys_id |      name         | title     | manager|
+--------+-------------------+-----------+--------+
| 555789 | Tina Belcher      | Contractor| 123456 | 
| 123456 | Bob Belcher       | Manager   | 654321 |
| 654321 | Calvin Fischoeder | SVP       | 997755 |
+--------+-------------------+-----------+--------+
SELECT su.Name
     , su.Title
     , dp.name
     , mg.name

FROM sys_user                   su
    LEFT JOIN cmn_department    dp
        ON dp.sys_id = su.department
    LEFT JOIN sys_user          mg
        ON mg.sys_id = su.manager

WHERE su.Title like ('%contractor%')

I appreciate any helps or tip that can be offered. Thanks for looking and have a great day.

Advertisement

Answer

Your Sys_User table is an adjacency list that only provides information about employees and who they directly report to. Adjacency Lists are one way to to encode hierarchical data. They’re nice because they’re relatively fast and compact, however they aren’t the only way to encode hierarchical relationships.

To answer the questions you are asking you will benefit from re-encoding the data into a Closure table which maps each employee to all of its direct and indirect managers/reportees along with their degree of separation, and any other additional pertinent info. However, since it represents a Many-to-Many relationship you don’t want to over load it with too much additional data. Fortunately thanks to the utility of recursive queries you can create one fairly easily on the fly.

To create a closure table you start by populating it with the degree 0 relationships, where every employee is considered their own manager/reportee. The reasoning for it is a bit beyond my ken, but it has something to do with the mathematics behind the concept of transitive closure (hence the name closure table). After that you iteratively (recursively) add each additional reporting degree. You can either do that from the Top Down, or from the Bottom Up

Here’s the Top Down version:

with closure(manager_id, report_id, degree, is_managing_SVP, is_reporting_svp) as (
  select sys_id
       , sys_id
       , 0
       , case when title like '%SVP%' then 1 else 0 end
       , case when title like '%SVP%' then 1 else 0 end
    from sys_user
  union all
  select cur.manager_id
       , nxt.sys_id
       , cur.degree+1
       , cur.is_managing_SVP
       , case when nxt.title like '%SVP%' then 1 else 0 end
    from closure cur
    join sys_user nxt
      on nxt.manager = cur.report_id
     and nxt.sys_id <> nxt.manager
)
select * from closure

And here’s the Bottom Up version:

with closure(manager_id, report_id, degree, is_managing_SVP, is_reporting_svp) as (
  select sys_id
       , sys_id
       , 0
       , case when title like '%SVP%' then 1 else 0 end
       , case when title like '%SVP%' then 1 else 0 end
    from sys_user
  union all
  select nxt.manager
       , cur.report_id
       , cur.degree+1
       , case when mgr.title like '%SVP%' then 1 else 0 end
       , cur.is_reporting_SVP
    from closure cur
    join sys_user nxt
      on nxt.sys_id = cur.manager_id
     and nxt.sys_id <> nxt.manager
    join sys_user mgr
      on mgr.sys_id = nxt.manager
)
select * from closure

It doesn’t matter too much which version you use if you are going to generate the entire closure table, however, if you want to optimize your query and only generate a partial closure table then it depends on if you want to walk up or down the tree.

Once generated you can use the closure table to answer your questions about SVPs such as who each contractor’s SVP is:

select r.sys_id, r.name, r.title, c.degree
     , c.manager_id SVP_ID
     , m.name SVP_name
     , m.title SVP_title
  from sys_user r
  join closure c
    on c.report_id = r.sys_id
  join sys_user m
    on m.sys_id = c.manager_id
 where r.title like '%contractor%'
   and c.is_managing_svp = 1
sys_id | name         | title      | degree | SVP_ID | SVP_name          | SVP_title
-----: | :----------- | :--------- | -----: | -----: | :---------------- | :--------
555789 | Tina Belcher | Contractor |      2 | 654321 | Calvin Fischoeder | SVP      

Or every direct and indirect report to the SVP named Calvin Fischoeder:

select m.sys_id manager_id
     , m.name
     , m.title
     , c.degree
     , r.sys_id report_id
     , r.name report_name
     , r.title report_title
  from sys_user m
  join closure c
    on c.manager_id = m.sys_id
  join sys_user r
    on r.sys_id = c.report_id
 where m.name = 'Calvin Fischoeder'
 order by degree, report_name
manager_id | name              | title | degree | report_id | report_name       | report_title
---------: | :---------------- | :---- | -----: | --------: | :---------------- | :-----------
    654321 | Calvin Fischoeder | SVP   |      0 |    654321 | Calvin Fischoeder | SVP         
    654321 | Calvin Fischoeder | SVP   |      1 |    123456 | Bob Belcher       | Manager     
    654321 | Calvin Fischoeder | SVP   |      2 |    555789 | Tina Belcher      | Contractor  

To see all queries in action, check out this db<>fiddle

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement