Распечатать все узлы в иерархии, если хотя бы один узел активен - PullRequest
0 голосов
/ 28 февраля 2020

Таблица EMPLOYEE имеет нижнюю структуру с 5 миллионами строк (5 * 10 6 ).

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

Самостоятельное соединение ManagerId в таблице вызывает несколько иерархий в системе. Иерархии будут на максимальном уровне 5.

Мне нужен оптимальный способ получить все узлы в иерархии, которые могут иметь хотя бы один узел в активном состоянии (1,2,3). Этот вывод будет сохранен в таблице с аналогичной структурой таблицы. Корней может быть несколько.

Предыдущая попытка использовала этот запрос, но он некорректен, поскольку не включал рекурсию.

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 (генеральный директор).

Аналогично, Emp3.1.2 (конечный узел), активен - поэтому включите всех менеджеров этого Emp3.1.2 (SubMgr3.1, Mgr3, CEO), даже если вышеперечисленные неактивны.

Ожидаемый результат (который должен оптимально храниться в другой таблице):

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

1 Ответ

0 голосов
/ 28 февраля 2020

Это получит полную нефильтрованную иерархию (при условии, что у менеджера верхнего уровня есть значение NULL для managerid):

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 |      <em>null</em> |      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 здесь

Вы также можете использовать рекурсивное условие подзапроса-факторинга для распространения активного состояния при переходе по иерархии; вам все равно нужно проверить оставшуюся иерархию в другом направлении, если она не активна. Профилируйте оба решения и посмотрите, является ли одно более производительным, чем другое, поскольку это не то, что мы можем определить.

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

...