воскресенье, 11 сентября 2011 г.

Иерархия. START WITH, CONNECT BY

В БД данные хранятся в виде двухмерных таблиц. Ниже рассмотрено построение из таблицы иерархической структуры (дерева).

Самый простой пример иерархии - штатная структура "руководитель - подчиненный".
Для примеров будет использоваться представление на основе таблиц HR.EMPLOYEES и HR.JOBS.
В общем виде выглядит как:
employee_idmanager_idjob_titlename
100PresidentKing, Steven
101100Administration Vice PresidentKochhar, Neena
102100Administration Vice PresidentDe Haan, Lex
103102ProgrammerHunold, Alexander
104103ProgrammerErnst, Bruce
105103ProgrammerAustin, David
106103ProgrammerPataballa, Valli
107103ProgrammerLorentz, Diana
108101Finance ManagerGreenberg, Nancy
109108AccountantFaviet, Daniel
...

START WITH Указывает какой узел считать корневым. Например:
START WITH employee_id = 100 отобразит иерархию всей организации
START WITH employee_id = 101 отобразит только ветку от сотрудника с employee_id = 101
CONNECT BY Связывает вышестоящую запись с нижестоящей.

PRIOR Указывает "направление" построения иерархии. Например запрос:
SELECT * from employees 
START WITH employee_id = 101
CONNECT BY MANAGER_ID = PRIOR EMPLOYEE_ID
Результат:
employee_idmanager_idjob_titlename
101100Administration Vice PresidentKochhar, Neena
108101Finance ManagerGreenberg, Nancy
109108AccountantFaviet, Daniel
...

Запрос:
SELECT * from employees 
START WITH employee_id = 101
CONNECT BY PRIOR MANAGER_ID = EMPLOYEE_ID
Результат:
employee_idmanager_idjob_titlename
101100Administration Vice PresidentKochhar, Neena
100PresidentKing, Steven

LEVEL Псевдополе показывающее уровень записи по отношению к корневому узлу:
SELECT LEVEL, EMPLOYEE_ID, MENEGER_IR, JOB_TITLE, NAME from employees 
START WITH employee_id = 101
CONNECT BY PRIOR MANAGER_ID = EMPLOYEE_ID
levelemployee_idmanager_idjob_titlename
1101100Administration Vice PresidentKochhar, Neena
2108101Finance ManagerGreenberg, Nancy
3109108AccountantFaviet, Daniel
3110108AccountantChen, John
...
При обратной иерархии нижний узел (указанный в START WITH) будет первый, а все вышестоящие нумероваться относительно него.

Для удобного представления данных можно LEVEL использовать с LPAD.
SELECT LEVEL, LPAD('-', level*2, '-') || JOB_TITLE JOB_TITLE from employees
start with employee_id = 101
connect by MANAGER_ID =  prior EMPLOYEE_ID
leveljob_title
1--Administration Vice President
2----Finance Manager
3------Accountant
3------Accountant
...

Функция SYS_CONNECT_BY_PATH отображает "путь" к должности (подобно вложенным каталогам в операционной системе). Например:
SELECT LEVEL, SYS_CONNECT_BY_PATH(JOB_TITLE, '\') JOB_TITLE, NAME from employees
start with employee_id = 101
connect by MANAGER_ID =  prior EMPLOYEE_ID
Результат:
leveljob_titlename
1\Administration Vice PresidentKochhar, Neena
2\Administration Vice President\Finance ManagerGreenberg, Nancy
3\Administration Vice President\Finance Manager\AccountantFaviet, Daniel
3\Administration Vice President\Finance Manager\AccountantChen, John
...

Функция CONNECT_BY_ROOT выводит для каждой записи значение корневой записи:
SELECT CONNECT_BY_ROOT JOB_TITLE ROOT, JOB_TITLE, NAME from employees
start with employee_id = 101
connect by MANAGER_ID =  prior EMPLOYEE_ID
Результат:
rootjob_titlename
Administration Vice PresidentAdministration Vice PresidentKochhar, Neena
Administration Vice PresidentFinance ManagerGreenberg, Nancy
Administration Vice PresidentAccountantFaviet, Daniel
Administration Vice PresidentAccountantChen, John
...

Условие WHERE убирает только отдельные записи:
select employee_id, manager_id, job_title, name from employees
where employee_id <> 108
start with employee_id = 101
connect by MANAGER_ID =  prior EMPLOYEE_ID
В данном случае "ушла" только запись по Greenberg, Nancy (где employee_id = 108) но нижестоящие записи остались:
employee_idmanager_idjob_titlename
101100Administration Vice PresidentKochhar, Neena
109108AccountantFaviet, Daniel
110108AccountantChen, John
200101Administration AssistantWhalen, Jennifer
...

Что бы убрать всю ветку необходимо условие задавать в CONNECT BY:
select employee_id, manager_id, job_title, name from employees
start with employee_id = 101
connect by MANAGER_ID =  prior EMPLOYEE_ID 
and employee_id <> 108
Ветка "фининсов" полностью скрыта:
employee_idmanager_idjob_titlename
101100Administration Vice PresidentKochhar, Neena
200101Administration AssistantWhalen, Jennifer
203101Human Resources RepresentativeMavris, Susan
...

Псевдополе CONNECT_BY_ISLEAF. Принимает значения 0 или 1. Показывает есть ли у данного сотрудника подчиненные. Конечные узлы, т.е. узны у которых нет нижестоящих еще называют листами.

С циклами (петлями) мощно бороться при помощи NOCYCLE. Под циклом понимается что один из узлов указан как вышестоящий для узла который расположен выше его по иерархии.

Сортировка ORDER BY работает со всей результирующей таблицей. Например сортировка по имени полностью разрушает иерархию. Во избежание этого использовать ORDER SIBLINGS BY. Эта сортировка производится в пределах одного уровня иерархии, таким образом не нарушая ее.