如何从自引用表

时间:2016-04-15 12:44:46

标签: sql hierarchy self-join pervasive-sql

我有以下表格:

Employees
-------------
ClockNo     int
CostCentre  varchar
Department  int

Departments
-------------
DepartmentCode  int
CostCentreCode  varchar
Parent          int

部门可以将其他部门作为父母,这意味着存在无限的层次结构。所有部门都属于成本中心,因此总是有CostCentreCode。如果parent = 0它是顶级部门

员工必须具有CostCentre值,但Department可能为0,表示他们不在某个部门

我想要尝试生成的是一个查询,它将提供最多四层次的层次结构。像这样:

EmployeesLevels
-----------------
ClockNo
CostCentre
DeptLevel1
DeptLevel2
DeptLevel3
DeptLevel4

我已经设法在其上显示部门结构,但我无法解决如何将其与员工联系而不会创建重复的员工行:

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创建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);

这给出了以下结构(方括号中的员工时钟数字):

Root
  |
  |---AAA                   [1]
  |    \---1                [6]
  |       |---2     
  |       \---3             [2]
  |          \---5          [7]
  |             \---12
  |                \---15   [8]
  |
  |---BBB                   [3]
  |    \---4                [4]
  |
  \---CCC                   [5]

查询应返回以下内容:

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  *

*对于员工8,他们处于第5级。理想情况下,我希望将所有级别显示为level4,但我很高兴在这种情况下显示CostCentre

7 个答案:

答案 0 :(得分:4)

当我们加入表格时,当我们找到属于上一级员工的适当部门时,我们应该停止进一步遍历路径。

当Employee.Department = 0时,我们也有例外情况。在这种情况下,我们不应该加入任何部门,因为在这种情况下,部门是根。

我们只需要在其中一个级别选择包含员工部门的记录。 如果员工的部门级别大于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;

答案 1 :(得分:2)

此处的主要挑战是员工的部门可能需要显示在 Level1 Level2 Level3 列中,或 Level4 ,具体取决于层次结构中该部门的上层数。

我建议首先在内部查询中查询每个员工的部门级别数,然后使用该信息将部门代码放在右栏中:

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

或者,您可以根据现有级别(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

答案 2 :(得分:2)

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]

答案 3 :(得分:1)

尝试此查询。不确定如何使用COALESCE来显示大型数据的性能。

我们的想法是构建一个派生的层次结构表,通向每个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

然后使用最右边的部门与Employees一起加入。这是完整的查询:

    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

答案 4 :(得分:1)

SunnyMagadan的查询很好。但是,根据部门中的员工数量,您可能希望尝试以下方法,使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
;

编辑关于5级以上的部门。

任何固定步骤查询都无法获得前4个级别。因此,更改上面的查询只是为了标记它们,例如-1。

, case when d4.Parent > 0 then NULL else 
    coalesce (d4.departmentcode, d3.departmentcode
            , d2.departmentcode, d1.departmentcode) end AS Level1

等等。

答案 5 :(得分:0)

我建议你分开查询以获取员工并获得他/她的部门层级。

为了获得部门的层次结构,我建议你使用这样的递归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

这不是你问题的直接答案,但这会给你选择和想法。希望这可以帮助。

答案 6 :(得分:0)

所以我已经采取了两个步骤来完成这项工作:

  1. 我必须递归地为deparments生成级别
  2. 生成所有可能的父节点,以便我可以在透视视图中显示它们
  3. 此递归查询构建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;
    

    输出结果:

    ╔════════════════╦════════════════╦════════╦═════════════════╗
    ║ DepartmentCode ║ CostCentreCode ║ Parent ║ DepartmentLevel ║
    ╠════════════════╬════════════════╬════════╬═════════════════╣
    ║              1 ║ AAA            ║      0 ║               1 ║
    ║              4 ║ BBB            ║      0 ║               1 ║
    ║              2 ║ AAA            ║      1 ║               2 ║
    ║              3 ║ AAA            ║      1 ║               2 ║
    ║              5 ║ AAA            ║      3 ║               3 ║
    ╚════════════════╩════════════════╩════════╩═════════════════╝
    

    现在,此查询将为每个节点生成所有可能的父节点(一种映射表):

    ;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;
    

    这给了我们这个结果:

    ╔════════════════╦════════════════╦════════╦═════════════════════╗
    ║ 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 ║
    ╚════════════════╩════════════════╩════════╩═════════════════════╝
    

    现在我们可以将这三个伙伴与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);
    

    它会给出这个:

    ╔═════════╦════════════╦════════╦════════╦════════╦════════╗
    ║ 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      ║        ║
    ╚═════════╩════════════╩════════╩════════╩════════╩════════╝
    

    此查询会根据DepartmentLevelCode找到相应的DepartmentCode,并会根据DepartmentLevel来调整内容。希望它是对的。