이번 칼럼은 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) 사용할 것