SQL 细分层次结构

SQL Breakdown Hierarchy

从基本的 Employee/Supervisor 层次结构开始,我使用递归 CTE 构建层次:

WITH EmployeeSupervisor
AS (
    SELECT *
    FROM (
        VALUES ('mike','lisa')
            ,('kevin','lisa')
            ,('lisa','ken')
            ,('ken','scott')
            ,('scott','chris')
            ,('chris','')
        ) RawData(emp, sup)
    )
    ,Hier
AS (
    -- anchor level, no supervisor
    SELECT 1 AS lvl
        ,emp
        ,sup
    FROM EmployeeSupervisor
    WHERE sup = ''
    
    UNION ALL
    
    -- recursive member
    SELECT H.lvl + 1 AS lvl
        ,ES.emp
        ,ES.sup
    FROM EmployeeSupervisor ES
    INNER JOIN Hier H
        ON ES.sup = H.emp
    WHERE H.lvl + 1 <= 5 -- max of 5 levels
        AND ES.sup != ''
    )
SELECT *
FROM Hier

我已经在 PIVOT 和 COALESCE 上尝试了一些变体以获得所需的输出(如下面的查询所示),但没有成功。

-- expected output
SELECT *
    FROM (
        VALUES ('mike','lisa','ken','scott','chris')
            ,('kevin','lisa','ken','scott','chris')
            ,('lisa','ken','scott','chris', NULL)
            ,('ken','scott','chris', NULL, NULL)
            ,('scott','chris', NULL, NULL, NULL)
            ,('chris',NULL, NULL, NULL, NULL)
        ) Expected(lvl1, lvl2,lvl3,lvl4,lvl5)

那里有很多类似的问题,但是 none 真正解决了这个问题的本质。

您需要修复递归查询,以便它正确地遍历关系 - 此外,您希望跟踪原始员工。

然后是对结果集进行透视的最后一步。为此,您可以在外部查询中使用条件聚合:

WITH hier AS (
    SELECT 1 AS lvl
        ,emp
        ,sup
    FROM EmployeeSupervisor
    WHERE sup = ''
    UNION ALL
    SELECT H.lvl + 1 AS lvl
        ,H.emp
        ,ES.sup
    FROM EmployeeSupervisor ES
    INNER JOIN Hier H
        ON H.sup = ES.emp
    WHERE H.lvl + 1 <= 5
)
SELECT 
    emp,
    MAX(CASE WHEN lvl = 1 THEN sup END) sup1,
    MAX(CASE WHEN lvl = 2 THEN sup END) sup2,
    MAX(CASE WHEN lvl = 3 THEN sup END) sup3,
    MAX(CASE WHEN lvl = 4 THEN sup END) sup4,
    MAX(CASE WHEN lvl = 5 THEN sup END) sup5
FROM Hier
GROUP BY emp


 

其实你可以换个角度看。 你要5级side-by-side,hard-wiring就5级吧。 您不妨将其构建为 hard-wired 5 向左自连接 ...

WITH
rawdata(ord,emp, sup) AS (  -- adding an order integer, to keep the order
          SELECT 1,'mike','lisa'
UNION ALL SELECT 2,'kevin','lisa'
UNION ALL SELECT 3,'lisa','ken'
UNION ALL SELECT 4,'ken','scott'
UNION ALL SELECT 5,'scott','chris'
UNION ALL SELECT 6,'chris',''
)
SELECT
  l0.emp
, l1.emp
, l2.emp
, l3.emp
, l4.emp
FROM      rawdata l0
LEFT JOIN rawdata l1 ON l0.sup=l1.emp
LEFT JOIN rawdata l2 ON l1.sup=l2.emp
LEFT JOIN rawdata l3 ON l2.sup=l3.emp
LEFT JOIN rawdata l4 ON l3.sup=l4.emp
ORDER BY l0.ord
;
-- out   emp  |  emp   |  emp   |  emp   |  emp  
-- out -------+--------+--------+--------+-------
-- out  mike  | lisa   | ken    | scott  | chris
-- out  kevin | lisa   | ken    | scott  | chris
-- out  lisa  | ken    | scott  | chris  | (null)
-- out  ken   | scott  | chris  | (null) | (null)
-- out  scott | chris  | (null) | (null) | (null)
-- out  chris | (null) | (null) | (null) | (null)

下一次尝试连接路径字符串,然后使用 SQL 服务器函数 TOKEN() 将路径拆分为列 ...

WITH RECURSIVE r AS (
  SELECT 
    1 AS lvl
  , emp AS path
  , *
  FROM rawdata
  WHERE sup=''
  UNION ALL
  SELECT
    p.lvl + 1
  , p.path + ',' + c.emp AS path
  , c.*
  FROM rawdata c
  JOIN r AS p
  ON c.sup = p.emp
)
SELECT
  TOKEN(path,',',1) AS s1
, TOKEN(path,',',2) AS s2
, TOKEN(path,',',3) AS s3
, TOKEN(path,',',4) AS s4
, TOKEN(path,',',5) AS s5
FROM r;
--   s1   |  s2   | s3  |  s4  |  s5   
-- -------+-------+-----+------+-------
--  chris |       |     |      | 
--  chris | scott |     |      | 
--  chris | scott | ken |      | 
--  chris | scott | ken | lisa | 
--  chris | scott | ken | lisa | mike
--  chris | scott | ken | lisa | kevin

您可以容纳所有以前的上级,并在 csv 字段中创建层次结构。 (检查我添加的第三个字段) 所以

  WITH EmployeeSupervisor
AS (
    SELECT *
    FROM (
        VALUES ('mike','lisa', 'lisa')
            ,('kevin','lisa', 'lisa')
            ,('lisa','ken', 'ken')
            ,('ken','scott', 'scott')
            ,('scott','chris', 'chris')
            ,('chris','', '')
        ) RawData(emp, sup, hierarchy)
    )
    ,Hier
AS (
    -- anchor level, no supervisor
    SELECT 1 AS lvl
        ,emp
        ,sup,
        cast (emp as varchar(255)) hierarchy
    FROM EmployeeSupervisor
    WHERE sup = ''

UNION ALL

-- recursive member
SELECT H.lvl + 1 AS lvl
    ,ES.emp
    ,ES.sup, 
    cast(ES.emp+', '+H.hierarchy as varchar(255))
FROM EmployeeSupervisor ES
INNER JOIN Hier H
    ON ES.sup = H.emp
WHERE H.lvl + 1 <= 5 -- max of 5 levels
    AND ES.sup != ''
)

SELECT *
FROM Hier

然后你会在最后一列中有这样的东西 mike,lisa,ken,scott,chris。 然后你需要分裂。例如使用这个

这个适合你吗?

PS: 由于类型不匹配,我认为强制转换是必要的,但您可以将其更改为更大的数字。

尝试从员工级别“向上”递归。并在中间 CTE 上放置额外的调试列,例如“路径”表达式:

WITH EmployeeSupervisor
AS (
    SELECT cast(emp as varchar(20)) employee, cast(sup as varchar(20)) supervisor
    FROM (
        VALUES ('mike','lisa')
            ,('kevin','lisa')
            ,('lisa','ken')
            ,('ken','scott')
            ,('scott','chris')
            ,('chris',null)
        ) RawData(emp, sup)
    )
    , hier AS (
    SELECT 1 AS lvl
        ,employee
        ,supervisor
        , cast(concat('',employee,'->',supervisor) as varchar(max)) path
        ,cast(supervisor as varchar(20)) sup1
        ,cast(null as varchar(20)) sup2
        ,cast(null as varchar(20)) sup3
        ,cast(null as varchar(20)) sup4
        ,cast(null as varchar(20)) sup5
    FROM EmployeeSupervisor
    UNION ALL
    SELECT H.lvl + 1 AS lvl
        ,H.employee employee
        ,es2.employee
        , cast(concat('',h.path,'->',es.supervisor) as varchar(max)) path
        ,null sup1
        ,case when H.lvl + 1 = 2 then cast(es2.employee as varchar(20)) end sup2
        ,case when H.lvl + 1 = 3 then cast(es2.employee as varchar(20)) end sup3
        ,case when H.lvl + 1 = 4 then cast(es2.employee as varchar(20)) end sup4
        ,case when H.lvl + 1 = 5 then cast(es2.employee as varchar(20)) end sup5
    FROM  Hier H
    join EmployeeSupervisor es
        ON H.supervisor = ES.employee
    join EmployeeSupervisor es2
        ON es.supervisor = es2.employee
    WHERE H.lvl + 1 <= 5
)

SELECT 
    employee,
    MAX(sup1) sup1,
    MAX(sup2) sup2,
    MAX(sup3) sup3,
    MAX(sup4) sup4,
    MAX(sup5) sup5
FROM Hier
GROUP BY employee

输出:

employee             sup1                 sup2                 sup3                 sup4                 sup5
-------------------- -------------------- -------------------- -------------------- -------------------- --------------------
chris                NULL                 NULL                 NULL                 NULL                 NULL
ken                  scott                chris                NULL                 NULL                 NULL
kevin                lisa                 ken                  scott                chris                NULL
lisa                 ken                  scott                chris                NULL                 NULL
mike                 lisa                 ken                  scott                chris                NULL
scott                chris                NULL                 NULL                 NULL                 NULL

如果你想要所有 emps 的层次结构,你必须从所有 emps 开始,而不仅仅是根。那么旋转就很简单了。

WITH EmployeeSupervisor
AS (
    SELECT *
    FROM (
        VALUES ('mike','lisa')
            ,('kevin','lisa')
            ,('lisa','ken')
            ,('ken','scott')
            ,('scott','chris')
            ,('chris','')
        ) RawData(emp, sup)
    )
    ,Hier
AS (
    -- all employees
    SELECT 1 AS lvl
        ,emp
        ,sup
    FROM EmployeeSupervisor

    UNION ALL
    
    -- recursive supervisors
    SELECT H.lvl + 1 AS lvl
        ,H.emp
        ,ES.sup
    FROM EmployeeSupervisor ES
    JOIN Hier H
      ON ES.emp = H.sup
    WHERE H.lvl  < 5 -- max of 5 levels
      AND ES.sup <> ''
    )
SELECT *
FROM Hier 
pivot (max(sup) 
       for lvl in ([1], [2], [3], [4], [5])
      ) as pvt

fiddle