如果至少有一个节点处于活动状态,则打印层次结构中的所有节点

Print all nodes in a hierarchy if at least one node is active

A table EMPLOYEE 具有以下结构,其中包含 500 万行 (5 * 106)。

Name   
------  
EMPNAME
EMPID
MANAGERID (foreign key to same table)
STATUS

table 上的 ManagerId 自联接导致系统中出现多个层次结构。层次结构最高为 5 级。

我需要一种最佳方法来获取层次结构中的所有节点,该层次结构可能至少有一个节点处于活动状态 (1,2,3)。 此输出将存储在具有类似 table 结构的 table 中。 可能有多个根。

之前曾尝试使用此查询,但它不正确,因为它不涉及递归。

SELECT empname, empid
FROM   employee e
WHERE e.status in (1,2,3)
      OR
      e.managerid IN  (SELECT empid 
                       FROM   employee m
                       WHERE  e.status in (1,2,3))

示例场景:

EMPNAME   | EMPID | MANAGERID | STATUS
:-------- | ----: | --------: | -----:
CEO       |     1 |   NULL    |      0
Mgr1      |     2 |      1    |      1
Mgr2      |     3 |      1    |      0
Mgr3      |     4 |      1    |      0
SubMgr1.1 |     5 |      2    |      0
SubMgr1.2 |     6 |      2    |      1
SubMgr2.1 |     7 |      3    |      0
SubMgr2.2 |     8 |      3    |      1
SubMgr3.1 |     9 |      4    |      0
Emp1.1.1  |    10 |      5    |      0
Emp1.1.2  |    11 |      5    |      1
Emp1.2.1  |    12 |      6    |      0
Emp1.2.2  |    13 |      6    |      1
Emp2.1.1  |    14 |      7    |      0
Emp2.1.2  |    15 |      7    |      1
Emp2.2.1  |    16 |      8    |      0
Emp2.2.2  |    17 |      8    |      1
Emp3.1.1  |    18 |      9    |      0
Emp3.1.2  |    19 |      9    |      1

在此示例中,Mgr1 的状态为 1 因此,应选择 Mgr1 下的所有员工和高于 Mgr1(CEO)的员工。

同样,Emp3.1.2(叶节点)处于活动状态 - 因此包括此 Emp3.1.2 的所有管理人员(SubMgr3.1、Mgr3、CEO),即使上述人员处于非活动状态。

预期输出(将最佳存储在不同的 table 中):

EMPNAME   | EMPID | MANAGERID | STATUS
:-------- | ----: | --------: | -----:
CEO       |     1 |      null |      0
Mgr1      |     2 |         1 |      1
SubMgr1.1 |     5 |         2 |      0
Emp1.1.1  |    10 |         5 |      0
Emp1.1.2  |    11 |         5 |      1
SubMgr1.2 |     6 |         2 |      1
Emp1.2.1  |    12 |         6 |      0
Emp1.2.2  |    13 |         6 |      1
Mgr2      |     3 |         1 |      0
SubMgr2.1 |     7 |         3 |      0
Emp2.1.2  |    15 |         7 |      1
SubMgr2.2 |     8 |         3 |      1
Emp2.2.1  |    16 |         8 |      0
Emp2.2.2  |    17 |         8 |      1
Mgr3      |     4 |         1 |      0
SubMgr3.1 |     9 |         4 |      0
Emp3.1.2  |    19 |         9 |      1

这将获得完整的未过滤层次结构(假设顶级经理具有 manageridNULL 值):

SELECT *
FROM   table_name
START WITH MANAGERID IS NULL
CONNECT BY PRIOR EMPID = MANAGERID

然后您可以过滤以查看层次结构中是否存在从每个员工备份到顶级经理的活动状态:

SELECT *
FROM   table_name t
WHERE EXISTS (
  SELECT 1
  FROM   table_name
  WHERE  status IN (1,2,3)
  START WITH EMPID = t.EMPID
  CONNECT BY EMPID = PRIOR MANAGERID
)
START WITH MANAGERID IS NULL
CONNECT BY PRIOR EMPID = MANAGERID

其中为测试数据:

CREATE TABLE table_name ( EMPNAME, EMPID, MANAGERID, STATUS ) AS
SELECT 'CEO',  1, NULL, 0 FROM DUAL UNION ALL
SELECT 'Mgr1', 2,    1, 1 FROM DUAL UNION ALL
SELECT 'Mgr2', 3,    1, 0 FROM DUAL UNION ALL
SELECT 'Mgr3', 4,    1, 0 FROM DUAL UNION ALL
SELECT 'SubMgr1.1', 5, 2, 0 FROM DUAL UNION ALL
SELECT 'SubMgr1.2', 6, 2, 1 FROM DUAL UNION ALL
SELECT 'SubMgr2.1', 7, 3, 0 FROM DUAL UNION ALL
SELECT 'SubMgr2.2', 8, 3, 1 FROM DUAL UNION ALL
SELECT 'SubMgr3.1', 9, 4, 0 FROM DUAL UNION ALL
SELECT 'Emp1.1.1', 10, 5, 0 FROM DUAL UNION ALL
SELECT 'Emp1.1.2', 11, 5, 1 FROM DUAL UNION ALL
SELECT 'Emp1.2.1', 12, 6, 0 FROM DUAL UNION ALL
SELECT 'Emp1.2.2', 13, 6, 1 FROM DUAL UNION ALL
SELECT 'Emp2.1.1', 14, 7, 0 FROM DUAL UNION ALL
SELECT 'Emp2.1.2', 15, 7, 1 FROM DUAL UNION ALL
SELECT 'Emp2.2.1', 16, 8, 0 FROM DUAL UNION ALL
SELECT 'Emp2.2.2', 17, 8, 1 FROM DUAL UNION ALL
SELECT 'Emp3.1.1', 18, 9, 0 FROM DUAL UNION ALL
SELECT 'Emp3.1.2', 19, 9, 1 FROM DUAL;

给出输出:

EMPNAME   | EMPID | MANAGERID | STATUS
:-------- | ----: | --------: | -----:
Mgr1      |     2 |         1 |      1
SubMgr1.1 |     5 |         2 |      0
Emp1.1.1  |    10 |         5 |      0
Emp1.1.2  |    11 |         5 |      1
SubMgr1.2 |     6 |         2 |      1
Emp1.2.1  |    12 |         6 |      0
Emp1.2.2  |    13 |         6 |      1
Emp2.1.2  |    15 |         7 |      1
SubMgr2.2 |     8 |         3 |      1
Emp2.2.1  |    16 |         8 |      0
Emp2.2.2  |    17 |         8 |      1
Emp3.1.2  |    19 |         9 |      1

要在两个方向上进行检查,则只需降低层次结构并提高层次结构即可:

SELECT *
FROM   table_name t
WHERE EXISTS (
  SELECT 1
  FROM   table_name
  WHERE  status IN (1,2,3)
  START WITH EMPID = t.MANAGERID
  CONNECT BY EMPID = PRIOR MANAGERID
  UNION ALL
  SELECT 1
  FROM   table_name
  WHERE  status IN (1,2,3)
  START WITH EMPID = t.EMPID
  CONNECT BY PRIOR EMPID = MANAGERID
)
START WITH MANAGERID IS NULL
CONNECT BY PRIOR EMPID = MANAGERID

输出:

EMPNAME   | EMPID | MANAGERID | STATUS
:-------- | ----: | --------: | -----:
CEO       |     1 |      null |      0
Mgr1      |     2 |         1 |      1
SubMgr1.1 |     5 |         2 |      0
Emp1.1.1  |    10 |         5 |      0
Emp1.1.2  |    11 |         5 |      1
SubMgr1.2 |     6 |         2 |      1
Emp1.2.1  |    12 |         6 |      0
Emp1.2.2  |    13 |         6 |      1
Mgr2      |     3 |         1 |      0
SubMgr2.1 |     7 |         3 |      0
Emp2.1.2  |    15 |         7 |      1
SubMgr2.2 |     8 |         3 |      1
Emp2.2.1  |    16 |         8 |      0
Emp2.2.2  |    17 |         8 |      1
Mgr3      |     4 |         1 |      0
SubMgr3.1 |     9 |         4 |      0
Emp3.1.2  |    19 |         9 |      1

db<>fiddle here

您还可以使用递归子查询分解子句在层次结构中下降时传播活动状态;如果它不活动,您仍然需要检查另一个方向的剩余层次结构。分析这两种解决方案,看看一个是否比另一个更高效,因为我们无法确定。

WITH data ( empname, empid, managerid, status, is_active ) AS (
  SELECT t.empname,
         t.empid,
         t.managerid,
         t.status,
         CASE WHEN status IN (1,2,3) THEN 1 ELSE 0 END
  FROM   table_name t
  WHERE  managerid IS NULL
UNION ALL
  SELECT t.empname,
         t.empid,
         t.managerid,
         t.status,
         CASE WHEN d.is_active = 1 OR t.status IN (1,2,3) THEN 1 ELSE 0 END
  FROM   data d
         INNER JOIN table_name t
         ON ( d.empid = t.managerid )
)
SELECT empname,
       empid,
       managerid,
       status
FROM   data d
WHERE  is_active = 1
OR     EXISTS (
  SELECT 1
  FROM   table_name t
  WHERE  t.status IN ( 1, 2, 3)
  START WITH d.empid = t.managerid
  CONNECT BY PRIOR empid = managerid
)

db<>fiddle