简体   繁体   English

如何从自引用表中确定每个人的结构

[英]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. 这里的主要挑战是员工的部门可能需要显示在Level1Level2Level3Level4列中 ,具体取决于层次结构中该部门的上层级别。

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

SQL Fiddle SQL小提琴

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

SQL Fiddle SQL小提琴

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: 所以我采取了两个步骤来完成这项工作:

  1. I had to generate levels for deparments recursively 我不得不递归地为deparments生成级别
  2. Generate all possible parent nodes so that I could display them in pivoted view 生成所有可能的父节点,以便我可以在透视视图中显示它们

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM