[ORACLE] 향상된 데이터 조회를 위한 정규표현식과 계층화 쿼리 1
![[ORACLE] 향상된 데이터 조회를 위한 정규표현식과 계층화 쿼리 1](/assets/img/study_Oracle/2022-10-11-%5BORACLE%5D_%ED%96%A5%EC%83%81%EB%90%9C%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%A1%B0%ED%9A%8C%EB%A5%BC%20%EC%9C%84%ED%95%9C%20%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D%EA%B3%BC%20%EA%B3%84%EC%B8%B5%ED%99%94%20%EC%BF%BC%EB%A6%AC%201/logo.png)
향상된 데이터 조회를 위한 정규표현식과 계층화 쿼리 1
계층화 쿼리(Hierarchical query)
- 계층화 쿼리
- 계층화 쿼리를 사용하면 테이블에 있는 행 간의 자연적 계층 관계(level)에 준하여 데이터를 검색할 수 있다.
- 자연적 계층 관계 = 가상 칼럼
 
 - 관계형 데이터베이스는 레코드를 계층 방식으로 저장하지 않는다.
- 따라서 대신에 계층적 모델을 만든다.
 
 - 그러나 한 테이블의 행 사이에 계층 관계가 존재하면 트리 탐색이라는 프로세스를 사용하여 계층을 구성할 수 있다.
 - 계층화 쿼리는 조회를 통한 보고의 한 방법으로, 특전 순서로 된 트리 분기가 있다.
 - 테이블의 행 사이에 관계를 통해 계층 표현이 가능하다.
 - 계층 트리는 계보(가계도), 가축류(품종 개량 목적), 기업 관리(관리 계층-조직도), 제조(제품 블리), 진화론 연구(종의 진화), 과학 연구 등의 여러 분야에 사용할 수 있다.
 
 - 계층화 쿼리를 사용하면 테이블에 있는 행 간의 자연적 계층 관계(level)에 준하여 데이터를 검색할 수 있다.
 
사원 테이블의 테이블 구조
- 자신의 사번은 employee_id, 상사의 사번은 manager_id로 표현한다.
 - 데이터 모델의 재귀적 관계인 경우 계층 쿼리 사용 가능
- 자기 자신과 관계를 맺음으로서 자기 자신의 기본키가 자기 자신에게 Foregin key로 전이되는 관계이다.
 
 - 사번과 상사의 사번의 값을 통해 자연 계층이 성립하여 계층화 쿼리가 사용 가능하게 된다.
- 상사와 부하직원 간의 계층 관계
 - 상품 분류 체계
 - 계층형 게시판
 



Level 1은root,Level 2는branch,Level 3은branch,Level 4는leaf.Level 2와Level 3은 밑에leaf가 있기 때문에branch가 된다.즉
Level칼럼은 employee_id, manager_id 두 개의 칼럼을 통해서 깊이에 의해 값을 읽어들일 수 있다. 
계층화 쿼리 문법
SELECT  [LEVEL], column, expr...
  FROM. table
[WHERE. condition(s)]
[START  WITH conditon(s)] -- 시작위치
[CONNECT BY PRIOR condition(s)];
- 시작 위치 : 충족해야 하는 조건을 지정
 
...START WITH last_name = 'Kochhar'
트리의 시작 위치라고 생각하면 된다.
WHERE 절 조건처럼 굳이 이름이 아니더라도 값을 나타낼 수 있으면 된다.
- 예
 
...START WITH manager_id IS NULL
...START WITH employee_id = (SELECT  employee_id
                               FROM. employees
                              WHERE. last_name = 'Kochhar')
서브쿼리를 넣어도 된다.
- 트리 탐색
 
CONNECT BY PRIOR column1 = column2
...CONNECT BY PRIOR employee_ID = manager_id -- 내 사번을 중심으로 상사의 사번을 읽어들임
...CONNECT BY PRIOR manager_id = employee_id
...CONNECT BY employee_id = PRIOR manager_id
탑-다운(하향식)으로 할 것인지, 바텀-업(상향식)으로 탐색을 할 것인지를 결정하는 것이 CONNECT BY 절의 핵심이다.
그 위치 값을 정하는 것이 PRIOR 이다.
PRIOR 가 붙은 칼럼이 부모 키가 된다고 생각하면 된다.
만약에 두 칼럼의 관계가 좀 애매하다고 생각이 된다면, 한 번은 왼쪽에, 한 번은 오른쪽에 두고 관찰해보자.
쉽게 관계를 파악할 수 있을 것이다.
계층화 쿼리 활용
SELECT. last_name || '는 ' || PRIOR last_name || '의 소속직원이다.' 
    AS  emp_report
  FROM. employees
 START. WITH last_name = 'King'
CONNECT BY PRIOR employee_id = manager_id;
| EMP_REPORT | |
|---|---|
| 1 | King는 의 소속직원이다. | 
| 2 | Kochhar는 King의 소속직원이다. | 
| 3 | Whalen는 Kochhar의 소속직원이다. | 
| 4 | Higgins는 Kochhar의 소속직원이다. | 
| … | … | 
SELECT 절에서 PRIOR 가 붙은 last_name 이 상사가 된다.
CONNECT BY 절에서 employee_id 에 PRIOR 가 붙었으므로 해당 칼럼이 부모 키가 된다.
계층화 쿼리를 이용한 계층 구조 표현
최상위 레벨에서 시작하여 다음 레벨을 각각 들여 쓴 형태의 회사 관리 레벨이 표시(조직도, organization chart)된 보고서를 생성하시오.
SELECT. LPAD(last_name, LENGTH(last_name)+(LEVEL*2)-2,'_') AS. org_chart FROM. employees START. WITH frist_name = 'Steven' AND.last_name = 'King' CONNECT BY PRIOR employee_id = manager_id;LPAD(char1, n [,char2])는 길이 n의 왼쪽을 char2의 문자 시퀀스로 채운 char1을 반환- 인수 n은 터미널 화면에 표시되는 반환 값의 총 길이
 LPAD(last_name, LENGTH(last_name)+(LEVEL*2)-2,'_')는 표시 형식을 정의- char1은 
LAST_NAME이고, n은 반환 값의 총 길이로LAST_NAME+(LEVEL*2)-2이고, char2는_이다. 
ORG_CHART 1 King 2 __Kochhar 3 ____Whalen 4 ____Higgins 5 ____Gietz 6 __De Haan … … 사원들의 이름, 부서명, 업무를 합쳐서 출력하고자 하는 계층형 쿼리 작성
SELECT. LPAD(last_name, LENGTH(last_name)+(LEVEL*3)-2,'_') || ' job is ' || job_id || ', department_name is ' || d.department_name AS. org_chart FROM. employees e, departments d WHERE. e.department_id = d.department_id START. WITH e.last_name = 'King' CONNECT.BY PRIOR e.employee_id = e.manager_id;ORG_CHART 1 _King job is AD_PRES, department_name is Executive 2 ____Kochhar job is AD_VP, department_name is Executive 3 ___Whalen job is AD_ASST, department_name is Administration 4 ___Higgins job is AC_MGR, department_name is Accounting … … 
계층화 쿼리 활용 : CONNECT_BY_ROOT() 함수
- CONNECT_BY_ROOT 함수는 현재 조회된 행들의 최상의 값을 출력하는 Pseudo column.
 
SELECT. last_name || '는 ' || PRIOR last_name
        || '의 소속직원이다. ' AS emp_report,
        CONNECT_BY_ROOT(employee_id) AS rootid,
        CONNECT_BY_ROOT(last_name) AS rootname
  FROM. employees
 START. WITH last_name = 'King'
CONNECT BY PRIOR employee_id = manager_id;
| EMP_REPORT | ROOTID | ROOTNAME | |
|---|---|---|---|
| 1 | King는 의 소속직원이다. | 100 | King | 
| 2 | Kochhar는 King의 소속직원이다. | 100 | King | 
| 3 | Whalen는 Kochhar의 소속직원이다. | 100 | King | 
| 4 | Higgins는 Kochhar의 소속직원이다. | 100 | King | 
| 5 | Gietz는 Higgins의 소속직원이다. | 100 | King | 
| … | … | … | … | 
계층화 쿼리의 고유 함수들은 계층 데이터가 있어야만 사용 가능하다.
즉 계층적 표현이 가능한 재귀 관계 모델에서만 사용할 수 있다.
계층화 쿼리 활용 : CONNECT_BY_ISLEAF() 함수
- CONNECT_BY_ISLEAF 함수는 대상 행이 Leaf라면 1을, 아니면 0을 출력하는 함수
 
SELECT. last_name || '는 ' || PRIOR last_name
        || '의 소속직원이다.' AS emp_report,
        CONNECT_BY_ISLEAF AS leaf
  FROM. employees
 START. WITH last_name = 'King'
CONNECT BY PRIOR employee_id = manager_id;
| EMP_REPORT | LEAF | |
|---|---|---|
| 1 | King는 의 소속직원이다. | 0 | 
| 2 | Kochhar는 King의 소속직원이다. | 0 | 
| 3 | Whalen는 Kochhar의 소속직원이다. | 1 | 
| 4 | Higgins는 Kochhar의 소속직원이다. | 0 | 
| … | … | … | 
계층화 쿼리 활용 : SYS_CONNECT_BY_PATH() 함수
- LEVEL 의사 열(Pseudo column)을 사용하지 말고, 각 사원의 전체 직속 계층을 표현하는 쿼리 작성 (예 : King-Kochhar-Greenberg-Faviet)
 
SELECT. substr(SYS_CONNECT_BY_PATH(last_name,'-'), 2)
    AS. name_path
  FROM. employees
 START. WITH last_name = 'King'
CONNECT BY PRIOR employee_id = manager_id;
| NAME_PATH | |
|---|---|
| 1 | King | 
| 2 | King-Kochhar | 
| 3 | King-Kochhar-Whalen | 
| 4 | King-Kochhar-Higgins | 
| 5 | King-Kochhar-Higgins-Gietz | 
| … | … | 
SYS-CONNECT-BY-PATH(column, char) 함수는 계층적 쿼리에서만 유효하며, 칼럼의 절대 경로를 char로 지정한 문자(-)로 분리하여 반환한다.
- 계층구조 전개를 부하직원은 사원번호 순으로 정렬하고자 한다
 
SELECT. employee_id
      , LEVEL lv
      , RPAD(' ', LEVEL*3-2) || last_name AS ename
      , SUBSTR(SYS_CONNECT_BY_PATH(last_name, '-'), 2) AS enames
  FROM. employees
 START. WITH manager_id IS NULL
CONNECT BY PRIOR employee_id = manager_id
 ORDER  BY employee_id;
| EMPLOYEE_ID | LV | ENAME | ENAMES | |
|---|---|---|---|---|
| 1 | 100 | 1 | King | King | 
| 2 | 101 | 2 | Kochhar | King-Kochhar | 
| 3 | 102 | 2 | De Haan | King-De Haan | 
| 4 | 103 | 3 | Hunold | King-De Haan-Hunold | 
| 5 | 104 | 4 | Ernst | King-De Haan-Hunold-Ernst | 
| … | … | … | … | … | 
ORDER BY 를 사용하여 정렬을 할 경우 결과가 이상하게 출력된다.
이 때는 SIBLINGS 를 추가하도록 하자.
ORDER SIBLINGS BY employee_id;
| EMPLOYEE_ID | LV | ENAME | ENAMES | |
|---|---|---|---|---|
| 1 | 100 | 1 | King | King | 
| 2 | 101 | 2 | Kochhar | King-Kochhar | 
| 3 | 200 | 3 | Whalen | King-Kochhar-Whalen | 
| 4 | 205 | 3 | Higgins | King-Kochhar-Higgins | 
| 5 | 206 | 4 | Gietz | King-Kochhar-Higgins-Gietz | 
| … | … | … | … | .. | 
SIBLINGS 는 계층쿼리에서만 사용된다.
의도에 맞는 정렬을 수행할 수 있다.
MS-SQL로 구현
- 계층화 쿼리에 사용되는 START WITH, CONNECT BY PRIOR 는 오라클 전용이다
 - MS-SQL로 구현 한다면?
- 굉장히 길어진다 …
 

 
계층화 쿼리 활용 : 트리의 가지(분기) 제거
- WHERE 절을 사용하여 노드를 제거
 
WHERE  last_name != 'Higgins'
가지인 Higgins 는 제거되지만, 그 자식 노드(Leaf)는 제거되지 않음에 유의.
- CONNECT BY 절을 사용하여 분기를 제거
 
CONNECT BY PRIOR employee_id = manager_id
           AND last_name != 'Higgins'
WHERE 절을 사용했을때와는 다르게 가지인 Higgins 와, 그 자식 노드, 리프까지 모두 제외된다.