계층형 쿼리(Hierarchical Query)는 오라클에서만 지원하고 있는 아주 막강한 기능 중의 하나다.
계층형 쿼리는 2차원 형태의 테이블에 저장된 데이터를 계층형 구조로 반환하는 쿼리를 말한다.
계층형 구조란 상하 수직 관계의 구조로 사원-대리-과장-부장과 같은 직급, 판매부-구매부 같은 부서 구조, 정부 부처등이 계층형 구조에 속한다. 또한 엑셀과 같은 프로그램의 메뉴 구조, 답변형 게시판뿐만 아니라 책의 목차도 이에 포함된다.
SELECT 컬럼1, 컬럼2...
FROM 테이블
WHERE 조건
START WITH 최상위 조건
CONNECT BY [NOCYCLE] [PRIOR 계층형 구조 조건]
START WITH MANAGER_ID IS NULL; // MANAGER_ID 가 NULL인 ROW가 최상위
START WITH JOB_CODE = 'J1'; // JOB_CODE 가 'J1'인 ROW가 최상위
CONNECT BY PRIOR 부모노드 = 자식노드
CONNECT BY 자식노드 = PRIOR 부모노드
// LEVEL : 계층형 쿼리에서만 사용할 수 있는 의사 컬럼으로 계층형 구조에 따른 레벨 값을 자동으로 반환
// LPAD : LEVEL 값에 따라 왼쪽에 공백을 붙인다.
SELECT LEVEL, LPAD(' ', 3*(LEVEL-1)) || D.DEPT_TITLE AS DEPT_TITLE,
J.JOB_NAME,
E.EMP_NAME,
E.EMP_ID,
E.MANAGER_ID
FROM EMPLOYEE E, JOB J, DEPARTMENT D
WHERE E.DEPT_CODE = D.DEPT_ID
AND E.JOB_CODE = J.JOB_CODE
START WITH E.MANAGER_ID IS NULL -- 널값부터 시작
CONNECT BY PRIOR E.EMP_ID = E.MANAGER_ID -- E.MANAGER_ID가 자식노드 E.EMP_ID가 부모노드
ORDER SIBLINGS BY J.JOB_CODE; -- 계층형 구조를 보존한 상태로 정렬
E.MANAGER_ID가 부모노드 E.EMP_ID가 자식노드인 상태로 계층이 형성된다.
즉, E.MANAGER_ID IS NULL인 대표가 최상위 노드가 되는 것이고, 이 때 대표의 EMP_ID는 200이다.
CONNECT BY PRIOR E.EMP_ID = E.MANAGER_ID에 의해 E.MANAGER_ID의 부모는 E.EMP_ID가 된다.
SELECT LEVEL, LPAD(' ', 3*(LEVEL-1)) || D.DEPT_TITLE AS DEPT_TITLE,
J.JOB_NAME,
E.EMP_NAME,
E.EMP_ID,
E.MANAGER_ID
FROM EMPLOYEE E, JOB J, DEPARTMENT D
WHERE E.DEPT_CODE = D.DEPT_ID
AND E.JOB_CODE = J.JOB_CODE
START WITH E.MANAGER_ID IS NULL
CONNECT BY PRIOR E.EMP_ID = E.MANAGER_ID
AND J.JOB_CODE < 'J5' -- 계층형 구조를 보존한 상태로 조건 추가
ORDER SIBLINGS BY J.JOB_CODE;
SELECT CONNECT_BY_ROOT D.DEPT_TITLE AS ROOT_DEPT , LEVEL, LPAD(' ', 3*(LEVEL-1)) || D.DEPT_TITLE AS DEPT_TITLE,
J.JOB_NAME,
E.EMP_NAME,
E.EMP_ID,
E.MANAGER_ID
FROM EMPLOYEE E, JOB J, DEPARTMENT D
WHERE E.DEPT_CODE = D.DEPT_ID
AND E.JOB_CODE = J.JOB_CODE
START WITH E.MANAGER_ID IS NULL
CONNECT BY PRIOR E.EMP_ID = E.MANAGER_ID
ORDER SIBLINGS BY J.JOB_CODE;
SELECT CONNECT_BY_ISLEAF, LEVEL, LPAD(' ', 3*(LEVEL-1)) || D.DEPT_TITLE AS DEPT_TITLE,
J.JOB_NAME,
E.EMP_NAME,
E.EMP_ID,
E.MANAGER_ID
FROM EMPLOYEE E, JOB J, DEPARTMENT D
WHERE E.DEPT_CODE = D.DEPT_ID
AND E.JOB_CODE = J.JOB_CODE
START WITH E.MANAGER_ID IS NULL
CONNECT BY PRIOR E.EMP_ID = E.MANAGER_ID
ORDER SIBLINGS BY J.JOB_CODE;
// SYS_CONNECT_BY_PATH(컬럼명, 구분기호)
SELECT SYS_CONNECT_BY_PATH(E.MANAGER_ID, '|') AS PATH, LEVEL, LPAD(' ', 3*(LEVEL-1)) || D.DEPT_TITLE AS DEPT_TITLE,
J.JOB_NAME,
E.EMP_NAME,
E.EMP_ID,
E.MANAGER_ID
FROM EMPLOYEE E, JOB J, DEPARTMENT D
WHERE E.DEPT_CODE = D.DEPT_ID
AND E.JOB_CODE = J.JOB_CODE
START WITH E.MANAGER_ID IS NULL
CONNECT BY PRIOR E.EMP_ID = E.MANAGER_ID
ORDER SIBLINGS BY J.JOB_CODE;
계층형 쿼리는 루프 알고리즘을 사용하기 때문에 조건을 잘못 줄 경우 무한루프를 타게 된다.
루프가 발생된 원인을 찾아 수정해야되는데 이 때, NOCYCLE을 추가하고 SELECT절에 CONNECT_BY_ISCYCLE 의사 컬럼을 사용하면 된다. CONNECT_BY_ISCYCLE 는 현재 로우가 자식을 갖고 있으면서 동시에 부모 로우이면 1을, 그렇지 않으면 0을 반환한다.
CREATE TABLE EX_TABLE AS
SELECT ROWNUM seq,
'2023' || LPAD(CEIL(ROWNUM/1000), 2, '0') MONTH,
// "||" 는 CONCAT
//CEIL 함수는 소수점 첫째 자리에서 올림하는 함수로, 주어진 숫자와 가장 근접한 큰 정수를 출력
//LPAD("값", "총 문자길이", "채움문자")
ROUND(DBMS_RANDOM.VALUE(100,1000)) AMT -- 난수를 생성하는 패키지
FROM DUAL
CONNECT BY LEVEL <= 12000;
// 명시한 숫자만큼의 로우를 반환함
// 내부적으로는 등비수열의 합만큼 로우를 생성한다.