[英]How can I Ascertain the structure for each person from a self referencing table
I have the following tables: 我有以下表格:
Employees
-------------
ClockNo int
CostCentre varchar
Department int
and 和
Departments
-------------
DepartmentCode int
CostCentreCode varchar
Parent int
Departments can have other departments as parents meaning there is infinite hierarchy. 部门可以将其他部门作为父母,这意味着存在无限的等级。 All departments belong to a cost centre and so will always have a
CostCentreCode
. 所有部门都属于成本中心,因此将始终具有
CostCentreCode
。 If parent = 0
it is a top level department 如果
parent = 0
,则它是顶级部门
Employees must have a CostCentre
value but may have a Department
of 0 meaning they are not in a department 员工必须具有
CostCentre
值,但可能具有0的Department
,这意味着他们不在部门中
What I want to try and generate is a query that will give the up to four levels of hierarchy. 我想要尝试生成的是一个查询,它将提供最多四层次的层次结构。 Like this:
像这样:
EmployeesLevels
-----------------
ClockNo
CostCentre
DeptLevel1
DeptLevel2
DeptLevel3
DeptLevel4
I've managed to get something to display the department structure on it's own, but I can't work out how to link this to the employees without creating duplicate employee rows: 我已经设法得到一些东西来显示它自己的部门结构,但我无法弄清楚如何在不创建重复的员工行的情况下将其链接到员工:
SELECT d1.Description AS lev1, d2.Description as lev2, d3.Description as lev3, d4.Description as lev4
FROM departments AS d1
LEFT JOIN departments AS d2 ON d2.parent = d1.departmentcode
LEFT JOIN departments AS d3 ON d3.parent = d2.departmentcode
LEFT JOIN departments AS d4 ON d4.parent = d3.departmentcode
WHERE d1.parent=0;
SQL To create Structure and some sample data: SQL要创建Structure和一些示例数据:
CREATE TABLE Employees(
ClockNo integer NOT NULL PRIMARY KEY,
CostCentre varchar(20) NOT NULL,
Department integer NOT NULL);
CREATE TABLE Departments(
DepartmentCode integer NOT NULL PRIMARY KEY,
CostCentreCode varchar(20) NOT NULL,
Parent integer NOT NULL
);
CREATE INDEX idx0 ON Employees (ClockNo);
CREATE INDEX idx1 ON Employees (CostCentre, ClockNo);
CREATE INDEX idx2 ON Employees (CostCentre);
CREATE INDEX idx0 ON Departments (DepartmentCode);
CREATE INDEX idx1 ON Departments (CostCentreCode, DepartmentCode);
INSERT INTO Employees VALUES (1, 'AAA', 0);
INSERT INTO Employees VALUES (2, 'AAA', 3);
INSERT INTO Employees VALUES (3, 'BBB', 0);
INSERT INTO Employees VALUES (4, 'BBB', 4);
INSERT INTO Employees VALUES (5, 'CCC', 0);
INSERT INTO Employees VALUES (6, 'AAA', 1);
INSERT INTO Employees VALUES (7, 'AAA', 5);
INSERT INTO Employees VALUES (8, 'AAA', 15);
INSERT INTO Departments VALUES (1, 'AAA', 0);
INSERT INTO Departments VALUES (2, 'AAA', 1);
INSERT INTO Departments VALUES (3, 'AAA', 1);
INSERT INTO Departments VALUES (4, 'BBB', 0);
INSERT INTO Departments VALUES (5, 'AAA', 3);
INSERT INTO Departments VALUES (12, 'AAA', 5);
INSERT INTO Departments VALUES (15, 'AAA', 12);
This gives the following structure (employee clock numbers in square brackets): 这给出了以下结构(员工时钟数字在方括号中):
Root
|
|---AAA [1]
| \---1 [6]
| |---2
| \---3 [2]
| \---5 [7]
| \---12
| \---15 [8]
|
|---BBB [3]
| \---4 [4]
|
\---CCC [5]
The query should return the following: 该查询应返回以下内容:
ClockNo CostCentre Level1 Level2 Level3 Level4
1 AAA
2 AAA 1 3
3 BBB
4 BBB 4
5 CCC
6 AAA 1
7 AAA 1 3 5
8 AAA 1 3 5 12 *
*
In the case of Employee 8, they are in level5. *
就员工8而言,他们处于第5级。 Ideally I would like to show all their levels down to level4, but I am happy just to show the CostCentre in this case 理想情况下,我希望将所有级别显示为level4,但我很高兴在这种情况下显示CostCentre
When we join the tables we should stop further traversal of the path when we found proper department that belongs to the Employee at previous level. 当我们加入表时,当我们找到属于上一级员工的适当部门时,我们应该停止进一步遍历路径。
Also we have exceptional case when Employee.Department=0. 当Employee.Department = 0时,我们也有例外情况。 In this case we shouldn't join any of departments, because in this case Department is the Root.
在这种情况下,我们不应该加入任何部门,因为在这种情况下,部门是根。
We need to choose only those records which contains employee's Department at one of the levels. 我们只需要在其中一个级别选择包含员工部门的记录。 In case if employee's department level is greater than 4 we should expand all 4 levels of departments and show them as is (even if can't reach the desired department level and didn't find it within expanded ones).
如果员工的部门级别大于4,我们应该扩展所有4个级别的部门并按原样显示它们(即使无法达到所需的部门级别并且未在扩展部门级别内找到它)。
select e.ClockNo,
e.CostCentre,
d1.DepartmentCode as Level1,
d2.DepartmentCode as Level2,
d3.DepartmentCode as Level3,
d4.DepartmentCode as Level4
from Employees e
left join Departments d1
on e.CostCentre=d1.CostCentreCode
and d1.Parent=0
and ((d1.DepartmentCode = 0 and e.Department = 0) or e.Department <> 0)
left join Departments d2
on d2.parent=d1.DepartmentCode
and (d1.DepartMentCode != e.Department and e.Department<>0)
left join Departments d3
on d3.parent=d2.DepartmentCode
and (d2.DepartMentCode != e.Department and e.Department<>0)
left join Departments d4
on d4.parent=d3.DepartmentCode
and (d3.DepartMentCode != e.Department and e.Department<>0)
where e.Department=d1.DepartmentCode
or e.Department=d2.DepartmentCode
or e.Department=d3.DepartmentCode
or e.Department=d4.DepartmentCode
or e.Department=0
or (
(d1.DepartmentCode is not null) and
(d2.DepartmentCode is not null) and
(d3.DepartmentCode is not null) and
(d4.DepartmentCode is not null)
)
order by e.ClockNo;
The main challenge here is that the employee's department might need to be displayed in column Level1 , Level2 , Level3 , or Level4 , depending on how many upper levels there are for that department in the hierarchy. 这里的主要挑战是员工的部门可能需要显示在Level1 , Level2 , Level3或Level4列中 ,具体取决于层次结构中该部门的上层级别。
I would suggest to first query the number of department levels there are for each employee in an inner query, and then to use that information to put the department codes in the right column: 我建议首先在内部查询中查询每个员工的部门级别数,然后使用该信息将部门代码放在右侧列中:
SELECT ClockNo, CostCentre,
CASE LevelCount
WHEN 1 THEN Dep1
WHEN 2 THEN Dep2
WHEN 3 THEN Dep3
ELSE Dep4
END Level1,
CASE LevelCount
WHEN 2 THEN Dep1
WHEN 3 THEN Dep2
WHEN 4 THEN Dep3
END Level2,
CASE LevelCount
WHEN 3 THEN Dep1
WHEN 4 THEN Dep2
END Level3,
CASE LevelCount
WHEN 4 THEN Dep1
END Level4
FROM (SELECT e.ClockNo, e.CostCentre,
CASE WHEN d2.DepartmentCode IS NULL THEN 1
ELSE CASE WHEN d3.DepartmentCode IS NULL THEN 2
ELSE CASE WHEN d4.DepartmentCode IS NULL THEN 3
ELSE 4
END
END
END AS LevelCount,
d1.DepartmentCode Dep1, d2.DepartmentCode Dep2,
d3.DepartmentCode Dep3, d4.DepartmentCode Dep4
FROM Employees e
LEFT JOIN departments AS d1 ON d1.DepartmentCode = e.Department
LEFT JOIN departments AS d2 ON d2.DepartmentCode = d1.Parent
LEFT JOIN departments AS d3 ON d3.DepartmentCode = d2.Parent
LEFT JOIN departments AS d4 ON d4.DepartmentCode = d3.Parent) AS Base
ORDER BY ClockNo
Alternatively, you could do a plain UNION ALL
of the 5 possible scenarios in terms of existing levels (chains of 0 to 4 departments): 或者,您可以根据现有级别(0到4个部门的链)在5个可能的场景中执行简单的
UNION ALL
:
SELECT ClockNo, CostCentre, d4.DepartmentCode Level1,
d3.DepartmentCode Level2, d2.DepartmentCode Level3,
d1.DepartmentCode Level4
FROM Employees e
INNER JOIN departments AS d1 ON d1.DepartmentCode = e.Department
INNER JOIN departments AS d2 ON d2.DepartmentCode = d1.Parent
INNER JOIN departments AS d3 ON d3.DepartmentCode = d2.Parent
INNER JOIN departments AS d4 ON d4.DepartmentCode = d3.Parent
UNION ALL
SELECT ClockNo, CostCentre, d3.DepartmentCode,
d2.DepartmentCode, d1.DepartmentCode, NULL
FROM Employees e
INNER JOIN departments AS d1 ON d1.DepartmentCode = e.Department
INNER JOIN departments AS d2 ON d2.DepartmentCode = d1.Parent
INNER JOIN departments AS d3 ON d3.DepartmentCode = d2.Parent
WHERE d3.Parent = 0
UNION ALL
SELECT ClockNo, CostCentre, d2.DepartmentCode,
d1.DepartmentCode, NULL, NULL
FROM Employees e
INNER JOIN departments AS d1 ON d1.DepartmentCode = e.Department
INNER JOIN departments AS d2 ON d2.DepartmentCode = d1.Parent
WHERE d2.Parent = 0
UNION ALL
SELECT ClockNo, CostCentre, d1.DepartmentCode Level1,
NULL, NULL, NULL
FROM Employees e
INNER JOIN departments AS d1 ON d1.DepartmentCode = e.Department
WHERE d1.Parent = 0
UNION ALL
SELECT ClockNo, CostCentre, NULL, NULL, NULL, NULL
FROM Employees e
WHERE e.Department = 0
ORDER BY ClockNo
SELECT [ClockNo]
, [CostCentre]
, CASE
WHEN Department <> 0 THEN dept.[Level1]
END AS [Level1]
, CASE
WHEN Department <> 0 THEN dept.[Level2]
END AS [Level2]
, CASE
WHEN Department <> 0 THEN dept.[Level3]
END AS [Level3]
, CASE
WHEN Department <> 0 THEN dept.[Level4]
END AS [Level4]
FROM [Employees] emp
LEFT JOIN
(
SELECT
CASE
WHEN d4.[DepartmentCode] IS NOT NULL THEN d4.[DepartmentCode]
WHEN d3.[DepartmentCode] IS NOT NULL THEN d3.[DepartmentCode]
WHEN d2.[DepartmentCode] IS NOT NULL THEN d2.[DepartmentCode]
ELSE d1.[DepartmentCode]
END AS [Level1]
, CASE
WHEN d4.[DepartmentCode] IS NOT NULL THEN d3.[DepartmentCode]
WHEN d3.[DepartmentCode] IS NOT NULL THEN d2.[DepartmentCode]
WHEN d2.[DepartmentCode] IS NOT NULL THEN d1.[DepartmentCode]
ELSE NULL
END AS [Level2]
, CASE
WHEN d4.[DepartmentCode] IS NOT NULL THEN d2.[DepartmentCode]
WHEN d3.[DepartmentCode] IS NOT NULL THEN d1.[DepartmentCode]
ELSE NULL
END AS [Level3]
, CASE
WHEN d4.[DepartmentCode] IS NOT NULL THEN d1.[DepartmentCode]
ELSE NULL
END AS [Level4]
, d1.[DepartmentCode] AS [DepartmentCode]
, d1.[CostCentreCode] AS [CostCenter]
FROM [Departments] d1
LEFT JOIN
[Departments] d2
ON d1.[Parent] = d2.[DepartmentCode]
LEFT JOIN
[Departments] d3
ON d2.[Parent] = d3.[DepartmentCode]
LEFT JOIN
[Departments] d4
ON d3.[Parent] = d4.[DepartmentCode]
) AS dept
ON emp.[Department] = dept.[DepartmentCode]
ORDER BY emp.[ClockNo]
Try this query. 试试这个查询。 Not sure how it will show itself performance-wise on large data with this
COALESCE
in place. 不知道如何使用这个
COALESCE
在大数据上表现出性能。
The idea is to build a derived table of hierarchies leading to each Department
我们的想法是构建一个派生的层次结构表,通向每个
Department
lev1 lev2 lev3 lev4
1 NULL NULL NULL
1 2 NULL NULL
1 3 NULL NULL
1 3 5 NULL
4 NULL NULL NULL
and then use rightmost department to join it with Employees
. 然后使用最右边的部门与
Employees
一起加入。 Here's the full query: 这是完整的查询:
SELECT
ClockNo,
CostCentre,
lev1,
lev2,
lev3,
lev4
FROM Employees
LEFT JOIN
(
SELECT
d1.DepartmentCode AS lev1,
NULL as lev2,
NULL as lev3,
NULL as lev4
FROM departments AS d1
WHERE d1.parent=0
UNION ALL
SELECT
d1.DepartmentCode AS lev1,
d2.DepartmentCode as lev2,
NULL as lev3,
NULL as lev4
FROM departments AS d1
JOIN departments AS d2 ON d2.parent = d1.departmentcode
WHERE d1.parent=0
UNION ALL
SELECT
d1.DepartmentCode AS lev1,
d2.DepartmentCode as lev2,
d3.DepartmentCode as lev3,
NULL as lev4
FROM departments AS d1
JOIN departments AS d2 ON d2.parent = d1.departmentcode
JOIN departments AS d3 ON d3.parent = d2.departmentcode
WHERE d1.parent=0
UNION ALL
SELECT
d1.DepartmentCode AS lev1,
d2.DepartmentCode as lev2,
d3.DepartmentCode as lev3,
d4.DepartmentCode as lev4
FROM departments AS d1
JOIN departments AS d2 ON d2.parent = d1.departmentcode
JOIN departments AS d3 ON d3.parent = d2.departmentcode
JOIN departments AS d4 ON d4.parent = d3.departmentcode
WHERE d1.parent=0
) Department
ON COALESCE(Department.lev4, Department.lev3, Department.lev2, Department.lev1) = Employees.Department
ORDER BY ClockNo
SunnyMagadan's query is good. SunnyMagadan的查询很好。 But depending on number of employees in a department you may wish to try the following one which leaves DB optimizer an opportunity to traverse department hierarchy only once for a department instead of repeating it for every employee in a department.
但是,根据部门中的员工数量,您可能希望尝试以下方法,使DB优化器有机会仅为部门遍历部门层次结构而不是为部门中的每个员工重复一次。
SELECT e.ClockNo, e.CostCentre, Level1, Level2, Level3, Level4
FROM Employees e
LEFT JOIN
(SELECT
d1.departmentcode
, d1.CostCentreCode
, coalesce (d4.departmentcode, d3.departmentcode
, d2.departmentcode, d1.departmentcode) AS Level1
, case when d4.departmentcode is not null then d3.departmentcode
when d3.departmentcode is not null then d2.departmentcode
when d2.departmentcode is not null then d1.departmentcode end as Level2
, case when d4.departmentcode is not null then d2.departmentcode
when d3.departmentcode is not null then d1.departmentcode end as Level3
, case when d4.departmentcode is not null then d1.departmentcode end as Level4
FROM departments AS d1
LEFT JOIN departments AS d2 ON d1.parent = d2.departmentcode
LEFT JOIN departments AS d3 ON d2.parent = d3.departmentcode
LEFT JOIN departments AS d4 ON d3.parent = d4.departmentcode) d
ON d.DepartmentCode = e.Department AND d.CostCentreCode = e.CostCentre
;
EDIT Regarding level 5+ departments. 编辑关于5级以上的部门。
Any fixed step query can not get top 4 levels for them. 任何固定步骤查询都无法获得前4个级别。 So change above query just to mark them some way, -1 for example.
所以更改上面的查询只是为了标记它们,例如-1。
, case when d4.Parent > 0 then NULL else
coalesce (d4.departmentcode, d3.departmentcode
, d2.departmentcode, d1.departmentcode) end AS Level1
and so on. 等等。
I would suggest that you seperate the query for getting the employee and getting his / her department hierarchy. 我建议您分离查询以获取员工并获得他/她的部门层级。
To get the hierarchy of the department, I would suggest you use recursive CTE something like this: 为了获得部门的层次结构,我建议你使用这样的递归CTE:
with DepartmentList (DepartmentCode, CostCentreCode, Parent) AS
(
SELECT
parentDepartment.DepartmentCode,
parentDepartment.CostCentreCode,
parentDepartment.Parent
FROM Departments parentDepartment
WHERE DepartmentCode = @departmentCode
UNION ALL
SELECT
childDepartment.DepartmentCode
childDepartment.CostCentreCode,
childDepartment.Parent,
FROM Departments childDepartment
JOIN DepartmentList
ON childDepartment.Parent = DepartmentList.DepartmentCode
)
SELECT * FROM DepartmentList
This is not the direct answer to your question, but this will give you option and idea. 这不是您问题的直接答案,但这会给您选择和想法。 Hope this helps.
希望这可以帮助。
So I've taken two steps in order to get this done: 所以我采取了两个步骤来完成这项工作:
This recursive query builds DepartmentLevels: 此递归查询构建DepartmentLevels:
;WITH CTE (DepartmentCode, CostCentreCode, Parent, DepartmentLevel)
AS (
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, 1
FROM dbo.Departments AS D
WHERE D.Parent = 0
UNION ALL
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, C.DepartmentLevel + 1
FROM dbo.Departments AS D
INNER JOIN CTE AS C
ON C.DepartmentCode = D.Parent
AND C.CostCentreCode = D.CostCentreCode
)
SELECT *
INTO #DepartmentLevels
FROM CTE;
That's the output: 这是输出:
╔════════════════╦════════════════╦════════╦═════════════════╗
║ DepartmentCode ║ CostCentreCode ║ Parent ║ DepartmentLevel ║
╠════════════════╬════════════════╬════════╬═════════════════╣
║ 1 ║ AAA ║ 0 ║ 1 ║
║ 4 ║ BBB ║ 0 ║ 1 ║
║ 2 ║ AAA ║ 1 ║ 2 ║
║ 3 ║ AAA ║ 1 ║ 2 ║
║ 5 ║ AAA ║ 3 ║ 3 ║
╚════════════════╩════════════════╩════════╩═════════════════╝
Now this query will generate all possible parent nodes for each node (a kind of a mapping table): 现在,此查询将为每个节点生成所有可能的父节点(一种映射表):
;WITH CTE (DepartmentCode, CostCentreCode, Parent, DepartmentLevelCode)
AS (
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, D.DepartmentCode
FROM dbo.Departments AS D
UNION ALL
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, C.DepartmentLevelCode
FROM dbo.Departments AS D
INNER JOIN CTE AS C
ON C.Parent = D.DepartmentCode
)
SELECT *
FROM CTE;
Which gives us this result: 这给了我们这个结果:
╔════════════════╦════════════════╦════════╦═════════════════════╗
║ DepartmentCode ║ CostCentreCode ║ Parent ║ DepartmentLevelCode ║
╠════════════════╬════════════════╬════════╬═════════════════════╣
║ 1 ║ AAA ║ 0 ║ 1 ║
║ 2 ║ AAA ║ 1 ║ 2 ║
║ 3 ║ AAA ║ 1 ║ 3 ║
║ 4 ║ BBB ║ 0 ║ 4 ║
║ 5 ║ AAA ║ 3 ║ 5 ║
║ 3 ║ AAA ║ 1 ║ 5 ║
║ 1 ║ AAA ║ 0 ║ 5 ║
║ 1 ║ AAA ║ 0 ║ 3 ║
║ 1 ║ AAA ║ 0 ║ 2 ║
╚════════════════╩════════════════╩════════╩═════════════════════╝
Now we can combine these three buddies together with Employees
table and get desired output: 现在我们可以将这三个伙伴与
Employees
表结合起来并获得所需的输出:
;WITH CTE (DepartmentCode, CostCentreCode, Parent, DepartmentLevelCode)
AS (
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, D.DepartmentCode
FROM dbo.Departments AS D
UNION ALL
SELECT D.DepartmentCode, D.CostCentreCode, D.Parent, C.DepartmentLevelCode
FROM dbo.Departments AS D
INNER JOIN CTE AS C
ON C.Parent = D.DepartmentCode
)
SELECT E.ClockNo
, E.CostCentre
, C.Level1
, C.Level2
, C.Level3
, C.Level4
FROM dbo.Employees AS E
OUTER APPLY (
SELECT MAX(CASE WHEN DL.DepartmentLevel = 1 THEN C.DepartmentCode END)
, MAX(CASE WHEN DL.DepartmentLevel = 2 THEN C.DepartmentCode END)
, MAX(CASE WHEN DL.DepartmentLevel = 3 THEN C.DepartmentCode END)
, MAX(CASE WHEN DL.DepartmentLevel = 4 THEN C.DepartmentCode END)
FROM CTE AS C
INNER JOIN #DepartmentLevels AS DL
ON DL.DepartmentCode = C.DepartmentCode
WHERE C.DepartmentLevelCode = E.Department
) AS C(Level1, Level2, Level3, Level4);
It will give this: 它会给出这样的:
╔═════════╦════════════╦════════╦════════╦════════╦════════╗
║ ClockNo ║ CostCentre ║ Level1 ║ Level2 ║ Level3 ║ Level4 ║
╠═════════╬════════════╬════════╬════════╬════════╬════════╣
║ 1 ║ AAA ║ ║ ║ ║ ║
║ 2 ║ AAA ║ 1 ║ 3 ║ ║ ║
║ 3 ║ BBB ║ ║ ║ ║ ║
║ 4 ║ BBB ║ 4 ║ ║ ║ ║
║ 5 ║ CCC ║ ║ ║ ║ ║
║ 6 ║ AAA ║ 1 ║ ║ ║ ║
║ 7 ║ AAA ║ 1 ║ 3 ║ 5 ║ ║
╚═════════╩════════════╩════════╩════════╩════════╩════════╝
This query will find coresponding DepartmentLevelCode
based on DepartmentCode
and will pivot stuff based on the DepartmentLevel
. 这个查询会发现coresponding
DepartmentLevelCode
基于DepartmentCode
并转动基础上,东西DepartmentLevel
。 Hopefully it's right. 希望它是对的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.