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 真正解决了这个问题的本质。
- 编辑:使用 SQL Server 2016,并希望避免大量重复加入有利于或递归 CTE。
您需要修复递归查询,以便它正确地遍历关系 - 此外,您希望跟踪原始员工。
然后是对结果集进行透视的最后一步。为此,您可以在外部查询中使用条件聚合:
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
从基本的 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 真正解决了这个问题的本质。
- 编辑:使用 SQL Server 2016,并希望避免大量重复加入有利于或递归 CTE。
您需要修复递归查询,以便它正确地遍历关系 - 此外,您希望跟踪原始员工。
然后是对结果集进行透视的最后一步。为此,您可以在外部查询中使用条件聚合:
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