[ORACLE] 향상된 데이터 조회를 위한 정규표현식과 계층화 쿼리 1
향상된 데이터 조회를 위한 정규표현식과 계층화 쿼리 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
와, 그 자식 노드, 리프까지 모두 제외된다.