이번 칼럼은 11g에서 추가된 Pivot & Unpivot query에 대해 소개해보고자 합니다.
우선 테스트를 위해 샘플 데이터를 만들어 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
-- 1. Check Oracle Version SELECT * FROM v$version ; BANNER ---------------------------------------------------------------------------- Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - 64bit Production PL/SQL Release 11.2.0.2.0 - Production -- 2. Create Table DROP TABLE EMP; CREATE TABLE EMP (EMPNO NUMBER(4) NOT NULL, ENAME VARCHAR2(10), JOB VARCHAR2(9), MGR NUMBER(4), HIREDATE DATE, SAL NUMBER(7, 2), COMM NUMBER(7, 2), DEPTNO NUMBER(2)) ; INSERT INTO EMP VALUES (7369, 'SMITH', 'CLERK', 7902, TO_DATE('17-DEC-1980', 'DD-MON-YYYY'), 800, NULL, 20); INSERT INTO EMP VALUES (7499, 'ALLEN', 'SALESMAN', 7698, TO_DATE('20-FEB-1981', 'DD-MON-YYYY'), 1600, 300, 30); INSERT INTO EMP VALUES (7521, 'WARD', 'SALESMAN', 7698, TO_DATE('22-FEB-1981', 'DD-MON-YYYY'), 1250, 500, 30); INSERT INTO EMP VALUES (7566, 'JONES', 'MANAGER', 7839, TO_DATE('2-APR-1981', 'DD-MON-YYYY'), 2975, NULL, 20); INSERT INTO EMP VALUES (7654, 'MARTIN', 'SALESMAN', 7698, TO_DATE('28-SEP-1981', 'DD-MON-YYYY'), 1250, 1400, 30); INSERT INTO EMP VALUES (7698, 'BLAKE', 'MANAGER', 7839, TO_DATE('1-MAY-1981', 'DD-MON-YYYY'), 2850, NULL, 30); INSERT INTO EMP VALUES (7782, 'CLARK', 'MANAGER', 7839, TO_DATE('9-JUN-1981', 'DD-MON-YYYY'), 2450, NULL, 10); INSERT INTO EMP VALUES (7788, 'SCOTT', 'ANALYST', 7566, TO_DATE('09-DEC-1982', 'DD-MON-YYYY'), 3000, NULL, 20); INSERT INTO EMP VALUES (7839, 'KING', 'PRESIDENT', NULL, TO_DATE('17-NOV-1981', 'DD-MON-YYYY'), 5000, NULL, 10); INSERT INTO EMP VALUES (7844, 'TURNER', 'SALESMAN', 7698, TO_DATE('8-SEP-1981', 'DD-MON-YYYY'), 1500, 0, 30); INSERT INTO EMP VALUES (7876, 'ADAMS', 'CLERK', 7788, TO_DATE('12-JAN-1983', 'DD-MON-YYYY'), 1100, NULL, 20); INSERT INTO EMP VALUES (7900, 'JAMES', 'CLERK', 7698, TO_DATE('3-DEC-1981', 'DD-MON-YYYY'), 950, NULL, 30); INSERT INTO EMP VALUES (7902, 'FORD', 'ANALYST', 7566, TO_DATE('3-DEC-1981', 'DD-MON-YYYY'), 3000, NULL, 20); INSERT INTO EMP VALUES (7934, 'MILLER', 'CLERK', 7782, TO_DATE('23-JAN-1982', 'DD-MON-YYYY'), 1300, NULL, 10); COMMIT; |
이제 Pivot query가 어떤 건지 데이터를 통해 확인해 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
-- 3. Query EMP col job format a10 col deptno format 999 col sum_sal format 999,999 SELECT JOB, DEPTNO, SUM(SAL) AS SUM_SAL FROM EMP GROUP BY JOB, DEPTNO ORDER BY JOB, DEPTNO ; JOB DEPTNO SUM_SAL ---------- ------ -------- ANALYST 20 6,000 CLERK 10 1,300 CLERK 20 1,900 CLERK 30 950 MANAGER 10 2,450 MANAGER 20 2,975 MANAGER 30 2,850 PRESIDENT 10 5,000 SALESMAN 30 5,600 |
위의 조회 결과는 EMP 테이블에서 JOB, DEPTNO 데이터를 기준으로 SAL 값을 SUM한 내용인데, 이 데이터를 JOB을 기준으로 DEPTNO 데이터별로 결과를 조회하고자 할 경우, 10g 버전에서는 아래와 같이 SQL을 구성해야 했습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
-- 4. Pivot in 10g version col job format a10 col sal_10 format 999,999 col sal_20 format 999,999 col sal_30 format 999,999 col sal_40 format 999,999 SELECT JOB, SUM(DECODE(DEPTNO, 10, SAL)) SAL_10, SUM(DECODE(DEPTNO, 20, SAL)) SAL_20, SUM(DECODE(DEPTNO, 30, SAL)) SAL_30, SUM(DECODE(DEPTNO, 40, SAL)) SAL_40 FROM EMP GROUP BY JOB ORDER BY 1 ; JOB SAL_10 SAL_20 SAL_30 SAL_40 ---------- -------- -------- -------- -------- ANALYST 6,000 CLERK 1,300 1,900 950 MANAGER 2,450 2,975 2,850 PRESIDENT 5,000 SALESMAN 5,600 |
즉, DEPTNO 값을 DECODE() 함수를 사용하여 가로로 나열한 후 SUM() 함수를 사용하여 SAL 데이터를 계산하였는데, 이 방식의 단점은 DEPTNO의 상수값을 미리 알고 있어야 하고, 이를 DECODE()를 통해 Select-List에 나열을 해야 해서, 직관적이지 못합니다.
이를 보완하기 위해 11g에서는 Pivot 기능을 위한 query를 할 수 있는 구문을 추가 하였으며, 그 구문은 아래와 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[Syntax] SELECT ... FROM ... PIVOT [XML] ( pivot_clause pivot_for_clause pivot_in_clause ) WHERE ... 1) pivot_clause : defines the columns to be aggregated (pivot is an aggregate operation); 2) pivot_for_clause : defines the columns to be grouped and pivoted; 3) pivot_in_clause : defines the filter for the column(s) in the pivot_for_clause (i.e. the range of values to limit the results to). The aggregations for each value in the pivot_in_clause will be transposed into a separate column (where appropriate). |
이제 아래 예제를 통해 사용방법을 살펴보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
SELECT * FROM (SELECT JOB, DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) -- pivot_clause FOR DEPTNO IN (10, 20, 30, 40) -- pivot_for_clause ) ORDER BY 1 ; JOB 10 20 30 40 ---------- ---------- ---------- ---------- ---------- ANALYST 6000 CLERK 1300 1900 950 MANAGER 2450 2975 2850 PRESIDENT 5000 SALESMAN 5600 |
위의 예제는 Pivot 구문을 사용한 예제인데, EMP 테이블에서 JOB, DEPTNO, SAL 데이터를 인라인 뷰에서 가져온 후, PVIOT 구문을 사용하여 JOB을 기준으로 집계해야 하는 컬럼을 pivot_clause에, 그리고 pviot 대상 컬럼을 pivot_for_clause에 위치시킵니다.
그리고 PIVOT 구문의 IN 절에서는 FOR 에 위치한 컬럼값을 필터랑 하는 역할을 하며, 이 때, SUM()을 사용하기 위해 GROUP BY 구문을 사용할 필요는 없습니다.
여기서 JOB, DEPTNO, SAL 3개 컬럼만 사용하였는데, 나머지 컬럼들을 보고 싶을 경우 어떻게 해야 할까요?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
SELECT * FROM EMP PIVOT (SUM(SAL) FOR DEPTNO IN (10, 20, 30, 40) ) ORDER BY 1 ; EMPNO ENAME JOB MGR HIREDATE COMM 10 20 30 40 ----- ------- ---------- ---- ------------ ---- ---------- ---------- ---------- ---------- 7369 SMITH CLERK 7902 17-DEC-80 800 7499 ALLEN SALESMAN 7698 20-FEB-81 300 1600 7521 WARD SALESMAN 7698 22-FEB-81 500 1250 7566 JONES MANAGER 7839 02-APR-81 2975 7654 MARTIN SALESMAN 7698 28-SEP-81 1400 1250 7698 BLAKE MANAGER 7839 01-MAY-81 2850 7782 CLARK MANAGER 7839 09-JUN-81 2450 7788 SCOTT ANALYST 7566 09-DEC-82 3000 7839 KING PRESIDENT 17-NOV-81 5000 7844 TURNER SALESMAN 7698 08-SEP-81 0 1500 7876 ADAMS CLERK 7788 12-JAN-83 1100 7900 JAMES CLERK 7698 03-DEC-81 950 7902 FORD ANALYST 7566 03-DEC-81 3000 7934 MILLER CLERK 7782 23-JAN-82 1300 |
만약 모든 컬럼을 보고 싶을 경우, Select-List에 모두 나열을 할 수 있으며, 다만 PIVOT 구문 안에 들어가는 컬럼들만 형태가 변합니다. 하지만 이렇게 SQL을 구현할 경우, SAL 컬럼을 SUM() 할 때 기준컬럼이 Unique 컬럼인 EMPNO도 들어가기 때문에 전체 건수가 다 나오므로 PIVOT을 사용하는 목적인 특정 컬럼을 GROUPING 하는 것은 못하게 되며, 10g일 경우 아래와 같을 것입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, COMM, DEPTNO, SUM(DECODE(DEPTNO, 10, SAL)) SAL_10, SUM(DECODE(DEPTNO, 20, SAL)) SAL_20, SUM(DECODE(DEPTNO, 30, SAL)) SAL_30, SUM(DECODE(DEPTNO, 40, SAL)) SAL_40 FROM EMP GROUP BY EMPNO, ENAME, JOB, MGR, HIREDATE, COMM, DEPTNO ORDER BY 1 ; EMPNO ENAME JOB MGR HIREDATE COMM DEPTNO SAL_10 SAL_20 SAL_30 SAL_40 ----- ------- ---------- ---- ------------ ---- ------ -------- -------- -------- -------- 7369 SMITH CLERK 7902 17-DEC-80 20 800 7499 ALLEN SALESMAN 7698 20-FEB-81 300 30 1,600 7521 WARD SALESMAN 7698 22-FEB-81 500 30 1,250 7566 JONES MANAGER 7839 02-APR-81 20 2,975 7654 MARTIN SALESMAN 7698 28-SEP-81 1400 30 1,250 7698 BLAKE MANAGER 7839 01-MAY-81 30 2,850 7782 CLARK MANAGER 7839 09-JUN-81 10 2,450 7788 SCOTT ANALYST 7566 09-DEC-82 20 3,000 7839 KING PRESIDENT 17-NOV-81 10 5,000 7844 TURNER SALESMAN 7698 08-SEP-81 0 30 1,500 7876 ADAMS CLERK 7788 12-JAN-83 20 1,100 7900 JAMES CLERK 7698 03-DEC-81 30 950 7902 FORD ANALYST 7566 03-DEC-81 20 3,000 7934 MILLER CLERK 7782 23-JAN-82 10 1,300 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
SELECT * FROM (SELECT JOB, DEPTNO, SAL FROM EMP) PIVOT (SUM(SAL) -- pivot_clause FOR DEPTNO IN (10, 20, 30, 40) -- pivot_for_clause ) WHERE JOB IN ('ANALYST','CLERK','SALESMAN') ORDER BY 1 ; JOB 10 20 30 40 ---------- ---------- ---------- ---------- ---------- ANALYST 6000 CLERK 1300 1900 950 SALESMAN 5600 |
3번째 예제는, PIVOT절로 구성된 데이터를 필터링하기 위해, WHERE절에서 기준컬럼 JOB에 조건을 추가할 수 있음을 확인하는 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
CREATE OR REPLACE VIEW PIVOT_EMP_V AS SELECT JOB, DEPTNO, SAL FROM EMP ; SELECT * FROM PIVOT_EMP_V PIVOT (SUM(SAL) AS SAL FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30, 40 AS D40) ) ORDER BY 1 ; JOB D10_SAL D20_SAL D30_SAL D40_SAL ---------- ---------- ---------- ---------- ---------- ANALYST 6000 CLERK 1300 1900 950 MANAGER 2450 2975 2850 PRESIDENT 5000 SALESMAN 5600 |
4번째 예제는, 기초집합을 PIVOT_EMP_V 뷰로 생성한 후, 이를 가지고 PIVOT 데이터를 만드는 예제인데, 이전과 크게 다르지 않은 예제이지만, PIVOT 컬럼에 Alias를 줄 수 있음을 확인할 수 있습니다.
Sample #3을 보면 Alias를 안 줄 경우 PIVOT 상수값을 컬럼 이름으로 출력하지만, 이번처럼 Alias를 줄 경우 출력 시 컬럼 이름이 부여됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
SELECT * FROM PIVOT_EMP_V PIVOT (SUM(SAL) AS SUM, COUNT(SAL) AS CNT FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30, 40 AS D40) ) ORDER BY 1 ; JOB D10_SUM D10_CNT D20_SUM D20_CNT D30_SUM D30_CNT D40_SUM D40_CNT ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ANALYST 0 6000 2 0 0 CLERK 1300 1 1900 2 950 1 0 MANAGER 2450 1 2975 1 2850 1 0 PRESIDENT 5000 1 0 0 0 SALESMAN 0 0 5600 4 0 |
5번째 예제는, PIVOT 절에 SUM() 이외에 COUNT() 함수를 추가한 예제인데, 이를 통해 PIVOT 절에는 한개 이상의 그룹함수 사용이 가능함을 알 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
SELECT * FROM PIVOT_EMP_V PIVOT (SUM(SAL) AS SUM, COUNT(SAL) AS CNT FOR (DEPTNO, JOB) IN ((30, 'SALESMAN') AS D30_SALESMAN, (30, 'MANAGER') AS D30_MANAGER, (30, 'CLERK') AS D30_CLERK) ) ORDER BY 1 ; D30_SALESMAN_SUM D30_SALESMAN_CNT D30_MANAGER_SUM D30_MANAGER_CNT D30_CLERK_SUM D30_CLERK_CNT ---------------- ---------------- --------------- --------------- ------------- ------------- 5600 4 2850 1 950 1 |
6번째 예제는, 재미난 Case인데 PIVOT FOR절에 2개 이상의 컬럼을 대상으로 할 경우, 이를 가로로 출력을 합니다. 즉, 이전 예제에서는 JOB을 기준으로 SAL 값을 DEPTNO별로 나열을 하였는데, 이렇게 2개를 지정할 경우 해당 데이터만 출력을 하고 추가로 세로로 표현하는 것이 아닌 가로로 데이터를 출력해 줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SELECT D30_SALESMAN_SUM, D30_SALESMAN_CNT FROM PIVOT_EMP_V PIVOT (SUM(SAL) AS SUM, COUNT(SAL) AS CNT FOR (DEPTNO, JOB) IN ((30, 'SALESMAN') AS D30_SALESMAN, (30, 'MANAGER') AS D30_MANAGER, (30, 'CLERK') AS D30_CLERK) ) ORDER BY 1 ; D30_SALESMAN_SUM D30_SALESMAN_CNT ---------------- ---------------- 5600 4 |
7번째 예제는, 6번째 예제와 FROM절 이하 구문이 동일하지만 Select-List에서 대상 컬럼들을 직접 지정하여 2개 컬럼만 출력하는 예제입니다. 여기서 주의할 점은 PIVOT 절 안의 Alias를 잘 파악하여 Select-List에 명시를 해주어야 하며, 그렇게 않을 경우 당연한 이야기지만 구문에러가 납니다.
이에 대해 아래 예제에서 살펴보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
SELECT DEPTNO FROM EMP PIVOT (SUM(SAL) AS SUM FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30, 40 AS D40) ) ORDER BY 1 ; SELECT DEPTNO * ERROR at line 1: ORA-00904: "DEPTNO": invalid identifier ; SELECT D10_SUM FROM EMP PIVOT (SUM(SAL) AS SUM FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30, 40 AS D40) ) ORDER BY 1 ; D10_SUM ---------- 1300 2450 5000 |
8번째 예제의 첫 번째 SQL에서는 Select-List를 잘 못 기입하여 구문 에러가 난 것을 확인할 수 있으며, 두 번째 SQL에서 ‘D10_SUM’이라고 표기하여 에러가 나지 않고 정상적인 결과가 출력됨을 알 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SELECT * FROM EMP PIVOT (--SUM(SAL) AS SUM SAL AS SUM FOR DEPTNO IN (10 AS D10, 20 AS D20, 30 AS D30, 40 AS D40) ) ORDER BY 1 ; SAL AS SUM * ERROR at line 4: ORA-56902: expect aggregate function inside pivot operation |
마지막 예제에서는, PIVOT 절에서 그룹 함수를 사용하지 않고 컬럼을 그냥 지정할 경우, 에러가 났음을 확인할 수 있으며, 이를 통해 반드시 그룹함수를 사용해야 함을 알 수 있습니다.
이상으로 11g에서 새롭게 소개된 PIVOT 함수에 대해 알아보았습니다. 다음 글에서는 PIVOT의 반대되는 개념인 UNPIVOT에 대해 소개하고자 하며, 이번 시간에는 여기서 글을 마치도록 하겠습니다.
reference site : http://www.oracle-developer.net/display.php?id=515
About the Author
● 경력 10년(현 LG전자 GERP DBA/Tuning 업무)
● 세미나 : 오라클 SQL 튜닝 방법의 이해, DBMS_XPLAN과 10046 Trace를 이용한 SQL문 튜닝, 개발자를 위한 어플리케이션튜닝 세미나
Related Posts
- [DB 칼럼] TRIGGER 사용방법 15-02-05 일반태그: Application Architecture. DB태그: Oracle.
- [DB 칼럼] LISTAGG Function (11g New Feature) 14-09-15 일반태그: Application Architecture. DB태그: Oracle.
댓글작성시 Code-Highlighter 삽입방법
Syntax [code title="..." theme="..."]coding...[/code]
Example [plsql title="현재시간 출력문" theme="classic"] select now() [/plsql]
Code-List plsql, mysql, java, objc, js, c#, c++, delphi, apache, php, css ...
Theme-List ado, arduino-ide, bncplusplus, cg-cookie, cisco-router, classic, eclipse, epicgeeks, familiar, feeldesign, github, idle, inlellij-idea, iris-vfx, mirc-dark, monokai, neon, secrets-of-rock, solarized-dark, solarized-light, son-of-obsidian, ssms2012, terminal, tomorrow, tomorrow-night, turnwall, twilight, vs2012 View Theme Demo
Alert 댓글에서 직접 코드 작성시 줄바꿈은 (Enter)값 대신 (Shift+Enter) 사용할 것