'02.Oracle'에 해당되는 글 248건

  1. 2018.06.19 mariadb 달력 테이블 만들기
  2. 2017.08.17 oracle 12c 사용자 생성시 ora - 65096
  3. 2017.06.02 oracle 스케줄 등록
  4. 2015.10.11 LISTAGG Analytic Function
  5. 2015.05.22 [펌] DBMS_STATS.GATHER_SYSTEM_STATS
  6. 2015.05.22 테이블의 정확한 통계 수집을 위해 미리 알려줘야할 정보를 수집하는 DBMS함수
  7. 2015.05.22 [펌] 통계정보 생성 하기 [ dbms_stats.gather_schema_stats Procedure] [출처] 통계정보 생성 하기 [ dbms_stats.gather_schema_stats Procedure]|
  8. 2015.05.22 [펌] dbms_stats.gather_table_stats 사용법
  9. 2015.01.20 [펌] [ORACLE/SQL] 셀렉트 후 업데이트, 수정 가능한 조인 뷰, SELECT-UPDATE, Modifiable Join View
  10. 2014.11.21 [펌] ORA-06508 PL/SQL: could not find program unit being called ~
  11. 2014.11.14 [펌] Oracle CURD Package Sample
  12. 2014.11.05 Orange windows 64bit ERROR:ORA-12154:TNS:couldnot resolve service name 2
  13. 2014.07.29 [펌] Oracle JDBC의 CachedRowSet Implement Bug
  14. 2014.07.23 [펌] [Oracle] MySQL의 Limit처럼 사용해보자.
  15. 2014.07.23 [펌] DBMS별 지원되는 최대 컬럼수(Oracle,ms-sql,mysql,db2)
  16. 2014.04.11 How to configure distributed transaction in Oracle Database Gateway for ODBC? (ORA-02047)
  17. 2014.04.11 [펌] ORA-02047: cannot join the distributed transaction in progress 1
  18. 2013.12.30 [펌] Sequence Cache 문제점
  19. 2013.02.06 [펌] 오라클 페이징 기법
  20. 2013.02.05 TOAD for Oracle Freeware 9.7
  21. 2012.10.06 [펌] Optimizer의 원리와 Tuning (상)
  22. 2012.09.21 Oracle] 통계 함수
  23. 2012.09.21 Oracle PIVOT
  24. 2012.09.21 pivot and unpivot queries in 11g
  25. 2012.09.21 Oracle over() 구문
  26. 2012.09.21 oracle pivot 데이터를 가로를 세로로...
  27. 2012.09.20 [ORACLE] 각종 관리상 팁
  28. 2012.09.18 orcale like 대신 instr
  29. 2012.09.18 SQL문 기초에서중급까지(L7)CASE문
  30. 2012.09.02 [펌] SQL Tuning Advisor
02.Oracle/DataBase2018. 6. 19. 10:33
반응형

call SP_B_GPS_REG_DATES( 

        '20170101',

        '20171231'

    );


--------------------------------------


CREATE OR REPLACE PROCEDURE `wbdb`.`GenerateRangeDates`(IN S_INDATE DATE, IN E_INDATE DATE)

BEGIN


    -- CREATE TEMPORARY TABLE IF NOT EXISTS dates (day DATE);

DELETE FROM B_GPS_REG_DATES_TMP;


    loopDate: LOOP

        INSERT INTO B_GPS_REG_DATES_TMP

        (

        YY, MM, DD, QUARTER, INDATE

        )

        VALUES 

        (

        -- SUBSTR(S_INDATE,1,4), SUBSTR(S_INDATE,6,2), SUBSTR(S_INDATE,9,2), QUARTER(S_INDATE), S_INDATE

SUBSTR(S_INDATE,1,4), SUBSTR(S_INDATE,6,2), SUBSTR(S_INDATE,9,2), QUARTER(S_INDATE), CONCAT(SUBSTR(S_INDATE,1,4), SUBSTR(S_INDATE,6,2), SUBSTR(S_INDATE,9,2))

        )

        ; 

        SET S_INDATE = DATE_ADD(S_INDATE, INTERVAL 1 DAY);


        IF S_INDATE <= E_INDATE 

            THEN ITERATE loopDate;

            ELSE LEAVE loopDate;

        END IF;

    END LOOP loopDate;


    -- SELECT day FROM dates;

    -- DROP TEMPORARY TABLE IF EXISTS dates;


END

Posted by 1010
02.Oracle2017. 8. 17. 10:24
반응형

alter session set "_ORACLE_SCRIPT"=true;

Posted by 1010
02.Oracle2017. 6. 2. 14:20
반응형

VARIABLE jobno number;

BEGIN

   DBMS_JOB.SUBMIT(

      :jobno, 

      'JOB_TRAFFIC_INFO();',

      SYSDATE,

      'TRUNC(SYSDATE+1)+7/24');

   COMMIT;

END;


Posted by 1010
02.Oracle2015. 10. 11. 16:50
반응형

String Aggregation Techniques

On occasion it is necessary to aggregate data from a number of rows into a single row, giving a list of data associated with a specific value. Using the SCOTT.EMP table as an example, we might want to retrieve a list of employees for each department. Below is a list of the base data and the type of output we would like to return from an aggregate query.

Base Data:

    DEPTNO ENAME
---------- ----------
        20 SMITH
        30 ALLEN
        30 WARD
        20 JONES
        30 MARTIN
        30 BLAKE
        10 CLARK
        20 SCOTT
        10 KING
        30 TURNER
        20 ADAMS
        30 JAMES
        20 FORD
        10 MILLER

Desired Output:

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

This article is based on a thread from asktom.oracle.com and contains several methods to achieve the desired results.

LISTAGG Analytic Function in 11g Release 2

The LISTAGG analytic function was introduced in Oracle 11g Release 2, making it very easy to aggregate strings. The nice thing about this function is it also allows us to order the elements in the concatenated list. If you are using 11g Release 2 you should use this function for string aggregation.

COLUMN employees FORMAT A50

SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

WM_CONCAT Built-in Function (Not Supported)

If you are not running 11g Release 2 or above, but are running a version of the database where the WM_CONCAT function is present, then it is a zero effort solution as it performs the aggregation for you. It is actually an example of a user defined aggregate function described below, but Oracle have done all the work for you.

COLUMN employees FORMAT A50

SELECT deptno, wm_concat(ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

3 rows selected.

 WM_CONCAT is an undocumented function and as such is not supported by Oracle for user applications (MOS Note ID 1336219.1). If this concerns you, use a User-Defined Aggregate Function described below.

Also, WM_CONCAT has been removed from 12c onward, so you can't pick this option.

User-Defined Aggregate Function

The WM_CONCAT function described above is an example of a user-defined aggregate function that Oracle have already created for you. If you don't want to use WM_CONCAT, you can create your own user-defined aggregate function as described at asktom.oracle.com.

CREATE OR REPLACE TYPE t_string_agg AS OBJECT
(
  g_string  VARCHAR2(32767),

  STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
    RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                       value  IN      VARCHAR2 )
     RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                         returnValue  OUT  VARCHAR2,
                                         flags        IN   NUMBER)
    RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                     ctx2  IN      t_string_agg)
    RETURN NUMBER
);
/
SHOW ERRORS


CREATE OR REPLACE TYPE BODY t_string_agg IS
  STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
    RETURN NUMBER IS
  BEGIN
    sctx := t_string_agg(NULL);
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                       value  IN      VARCHAR2 )
    RETURN NUMBER IS
  BEGIN
    SELF.g_string := self.g_string || ',' || value;
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                         returnValue  OUT  VARCHAR2,
                                         flags        IN   NUMBER)
    RETURN NUMBER IS
  BEGIN
    returnValue := RTRIM(LTRIM(SELF.g_string, ','), ',');
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                     ctx2  IN      t_string_agg)
    RETURN NUMBER IS
  BEGIN
    SELF.g_string := SELF.g_string || ',' || ctx2.g_string;
    RETURN ODCIConst.Success;
  END;
END;
/
SHOW ERRORS


CREATE OR REPLACE FUNCTION string_agg (p_input VARCHAR2)
RETURN VARCHAR2
PARALLEL_ENABLE AGGREGATE USING t_string_agg;
/
SHOW ERRORS

The aggregate function is implemented using a type and type body, and is used within a query.

COLUMN employees FORMAT A50

SELECT deptno, string_agg(ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

3 rows selected.

Specific Function

One approach is to write a specific function to solve the problems. The get_employees function listed below returns a list of employees for the specified department.

CREATE OR REPLACE FUNCTION get_employees (p_deptno  in  emp.deptno%TYPE)
  RETURN VARCHAR2
IS
  l_text  VARCHAR2(32767) := NULL;
BEGIN
  FOR cur_rec IN (SELECT ename FROM emp WHERE deptno = p_deptno) LOOP
    l_text := l_text || ',' || cur_rec.ename;
  END LOOP;
  RETURN LTRIM(l_text, ',');
END;
/
SHOW ERRORS

The function can then be incorporated into a query as follows.

COLUMN employees FORMAT A50

SELECT deptno,
       get_employees(deptno) AS employees
FROM   emp
GROUP by deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.

To reduce the number of calls to the function, and thereby improve performance, we might want to filter the rows in advance.

COLUMN employees FORMAT A50

SELECT e.deptno,
       get_employees(e.deptno) AS employees
FROM   (SELECT DISTINCT deptno
        FROM   emp) e;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES
        
3 rows selected.

Generic Function using Ref Cursor

An alternative approach is to write a function to concatenate values passed using a ref cursor. This is essentially the same as the previous example, except that the cursor is passed in making it generic, as shown below.

CREATE OR REPLACE FUNCTION concatenate_list (p_cursor IN  SYS_REFCURSOR)
  RETURN  VARCHAR2
IS
  l_return  VARCHAR2(32767); 
  l_temp    VARCHAR2(32767);
BEGIN
  LOOP
    FETCH p_cursor
    INTO  l_temp;
    EXIT WHEN p_cursor%NOTFOUND;
    l_return := l_return || ',' || l_temp;
  END LOOP;
  RETURN LTRIM(l_return, ',');
END;
/
SHOW ERRORS

The CURSOR function is used to allow a query to be passed to the function as a ref cursor, as shown below.

COLUMN employees FORMAT A50

SELECT e1.deptno,
       concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employees
FROM   emp e1
GROUP BY e1.deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.

Once again, the total number of function calls can be reduced by filtering the distinct values, rather than calling the function for each row.

COLUMN employees FORMAT A50

SELECT deptno,
       concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employees
FROM   (SELECT DISTINCT deptno
        FROM emp) e1;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.

ROW_NUMBER() and SYS_CONNECT_BY_PATH functions in Oracle 9i

An example on williamrobertson.net uses the ROW_NUMBER() and SYS_CONNECT_BY_PATH functions to achieve the same result without the use of PL/SQL or additional type definitions.

SELECT deptno,
       LTRIM(MAX(SYS_CONNECT_BY_PATH(ename,','))
       KEEP (DENSE_RANK LAST ORDER BY curr),',') AS employees
FROM   (SELECT deptno,
               ename,
               ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) AS curr,
               ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) -1 AS prev
        FROM   emp)
GROUP BY deptno
CONNECT BY prev = PRIOR curr AND deptno = PRIOR deptno
START WITH curr = 1;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

COLLECT function in Oracle 10g

An example on oracle-developer.net uses the COLLECT function in Oracle 10g to get the same result. This method requires a table type and a function to convert the contents of the table type to a string. I've altered his method slightly to bring it in line with this article.

CREATE OR REPLACE TYPE t_varchar2_tab AS TABLE OF VARCHAR2(4000);
/

CREATE OR REPLACE FUNCTION tab_to_string (p_varchar2_tab  IN  t_varchar2_tab,
                                          p_delimiter     IN  VARCHAR2 DEFAULT ',') RETURN VARCHAR2 IS
  l_string     VARCHAR2(32767);
BEGIN
  FOR i IN p_varchar2_tab.FIRST .. p_varchar2_tab.LAST LOOP
    IF i != p_varchar2_tab.FIRST THEN
      l_string := l_string || p_delimiter;
    END IF;
    l_string := l_string || p_varchar2_tab(i);
  END LOOP;
  RETURN l_string;
END tab_to_string;
/

The query below shows the COLLECT function in action.

COLUMN employees FORMAT A50

SELECT deptno,
       tab_to_string(CAST(COLLECT(ename) AS t_varchar2_tab)) AS employees
FROM   emp
GROUP BY deptno;
       
    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES
        
3 rows selected.


Posted by 1010
02.Oracle/DataBase2015. 5. 22. 14:18
반응형

출처 : [한국 Oracle Technology Network]의 기술 지원 게시판

 

========================================================================================

No. 22383 : DBMS_STATS.GATHER_SYSTEM_STATS를 이용한 통계정보 수집 가이드
========================================================================================

 

PURPOSE
-------

 

   이 자료는 DBMS_STATS.GATHER_SYSTEM_STATS 함수를 사용하여 system table에 대한

   통계 정보 수집 시 stats table과 AUX_STATS$ table에 나타나는 정보에 대한 분석 및 
   통계 정보 생성 시 필요한 INTERVAL에 대한 내용과 통계정보 수집 상태를 나타내는

   NOWORKLOAD, BADSTATS, AUTOGATHERING 상태에 대한 설명입니다.


Explanation
-----------

 

다음과 같은 문서를 참고하여 test 후, 확인하였습니다.
<Note:153761.1> Gathering System Statistics Returns only 'NOWORKLOAD'.
<Note:149560.1> Collect and Display System Statistics (CPU and IO) for CBO usage.

 

DBMS_STATS.GATHER_SYSTEM_STATS를 사용하여 20분 동안의 통계정보를 생성하는

간단한 절차를 test를 통해 알아보기로 한다. 통계정보 수집 시간이 최소 권장 사항인 60분

이상이 되지 않아도 오라클에서 통계정보를 측정하기 위한 기준치 만큼의 자원 소비가 있으면 
PVAL2가 badstats로 나오지 않고 autogathering으로 정상으로 나오는 것을 알 수 있다.


Step 
-----
1. sqlplus "/as sysdba"
2. EXECUTE DBMS_STATS.CREATE_STAT_TABLE( ownname => 'SYSTEM',

    stattab => 'mystats'); 
3. ALTER SYSTEM SET JOB_QUEUE_PROCESSES = 1;

4. EXECUTE DBMS_STATS.GATHER_SYSTEM_STATS( interval => 20, stattab => 'mystats',

     statid => 'DAY', statown => 'SYSTEM'); 
    EXECUTE DBMS_STATS.GATHER_SYSTEM_STATS('INTERVAL', 20);

 

< 참고 > statid 로 DAY 와 NIGHT 이 있으므로, DBMS_STATS.GATHER_SYSTEM_STATS 
             함수의 파라미터로 선택하여 사용 가능함.

 

5. select * from system.mystats; 
    select count(*) from system.mystats;

 

   위의 select 수행 결과 no rows selected로 나오면 아래와 같이 수행한다. 

   EXECUTE DBMS_STATS.GATHER_SYSTEM_STATS( gathering_mode=>'START',

    stattab => 'mystats', statid => 'DAY', statown => 'SYSTEM');

 

   또한 START 를 MANUAL하게 해주지 않으면 다음과 같이 NOWORKLOAD로 나타난다.

 

SQL> select PNAME, PVAL2 from SYS.AUX_STATS$ where pname ='STATUS';

 

PNAME     PVAL2
---------  --------------------------------------------------------------------
STATUS    NOWORKLOAD


6. DBMS_STATS.GATHER_SYSTEM_STATS 함수에서 START 를 해주면 아래와 같이 
   AUTOGATHERING 상태로 바뀐다.

 

SQL> select PNAME, PVAL2 from SYS.AUX_STATS$ where pname ='STATUS';

 

PNAME    PVAL2
--------  ---------------------------------------------------------------------
STATUS   AUTOGATHERING


7. 아직 20분이 경과되지 않으면 아래와 같이 진행 상황을 확인할 수 있다.

 

SQL> alter session set nls_date_format='mm-dd-YYYY HH24:MI';
SQL> select c1, to_date(c2), to_date(c3) from system.mystats;

 

C1                               TO_DATE(C2)        TO_DATE(C3)
--------------------------------------------------------------
MANUALGATHERING      12-21-2004 02:46   12-21-2004 02:46
 

8. MANUAL하게 STOP을 하려면 아래와 같이 수행한다.

 

EXECUTE DBMS_STATS.GATHER_SYSTEM_STATS( gathering_mode=>'STOP',

stattab => 'mystats', statid => 'DAY', statown => 'SYSTEM'); 


<주의> 만약, DBMS_STATS.GATHER_SYSTEM_STATS 함수를 이용하여 STOP을
실행한 이후에 아래 9번과 같은 명령을 수행 시 PVAL2가 badstats 로 나온다면 이것은

interval 이 충분하지 않아 그 기간 동안에 작업 WORKLOAD가 부족하기 때문입니다.


따라서, interval 동안 SINGLE BLOCK I/O와 multiblock I/O에 대한 workload가 오라클에서

통계정보를 측정하기 위한 기준치 만큼의 자원 소비가 없으면 정보가 부족하여 발생하는

현상으로 보입니다. 즉, 지속적인 i/o에 대한 통계치를 제대로 수집하지 못해서 발생하는

것입니다. 시스템에 대한 통계 정보 수집 시 interval은 최소한 60분, default로 120분입니다. 
  

9. 20분이 지나면 아래와 같은 spool 결과를 볼 수 있고, sys.aux_stats$ table에서

    PVAL2 가 AUTOGATHERING에서 COMPLETED 로 변경됨을 알 수 있다. 
     
SQL> select SNAME, PNAME, PVAL1, PVAL2 from SYS.AUX_STATS$

         where pname ='STATUS';

 

SNAME                  PNAME              PVAL1        PVAL2                    
--------------------------------------------------------------------------------
SYSSTATS_INFO     STATUS                               COMPLETED     


10. 통계정보를 보관하기 위해 생성한 'mystats' 라는 stats table을 drop하려면 
     이와 같이 DBMS_STATS.DROP_STAT_TABLE procedure를 수행한다. 

 

EXECUTE DBMS_STATS.DROP_STAT_TABLE( ownname => 'SYSTEM', stattab =>'mystats');

 

< 권장 사항 >

dbms_stats.gather_system_stats package 사용 시 이 두 개의 파라미터를 Tuning 후

parameter file에 설정하면 보다 나은 CBO의 효과를 얻을 수 있다.

  ~ optimizer_index_caching  (range of value : 0 to 100)
  ~ optimizer_index_cost_adj (range : 1 to 10000)


< 참고 >

이와 같은 시스템 통계정보 생성 작업을 dbms_job.submit 프로시져를 이용하여 수행할 수도 있다.

이렇게 job으로 등록했을 경우에는 통계정보 생성 중에 dba_jobs 와 dba_jobs_running 뷰를

조인하여 확인해 보면 WHAT 컬럼에 아래와 같은 job scheduling이 걸리는 것을 볼 수 있다.

 

sql>dbms_stats.gather_system_stats(gathering_mode => 'AUTO_STOP', statown => 'SYS');


Example
----------

위의 작업에 대한 spool on 수행 결과입니다.

 

SQL> select * from sys.aux_stats$;

SQL> SELECT count(*) from system.mystats;

SQL> select job, what from user_jobs;                                    
SQL> select PNAME, PVAL2 from SYS.AUX_STATS$ where pname ='STATUS';

SQL> select * from sys.aux_stats$;

SQL> exec DBMS_STATS.IMPORT_SYSTEM_STATS ('mystats','DAY','SYSTEM');

BEGIN DBMS_STATS.IMPORT_SYSTEM_STATS ('mystats','DAY','SYSTEM'); END;

*
ERROR at line 1:
ORA-20003: Unable to import system statistics 
ORA-06512: at "SYS.DBMS_STATS", line 5437 
ORA-06512: at line 1


SQL> EXECUTE DBMS_STATS.GATHER_SYSTEM_STATS( gathering_mode=>'STOP',

          stattab => 'mystats', statid => 'DAY', statown => 'SYSTEM');

 

SQL> select c1, to_date(c2), to_date(c3) from system.mystats;

C1                             TO_DATE(C2)      TO_DATE(C3)                     
------------------------------ ---------------- ----------------                
COMPLETED              12-21-2004 02:46   12-21-2004 03:10                
                                                                               

SQL> select SNAME, PNAME, PVAL1, PVAL2 from SYS.AUX_STATS$

         where pname ='STATUS';

 

SQL> EXECUTE DBMS_STATS.GATHER_SYSTEM_STATS( gathering_mode=>'START',

         stattab => 'mystats', statid => 'DAY', statown => 'SYSTEM');

 

SQL> select PNAME, PVAL2 from SYS.AUX_STATS$ where pname ='STATUS';

                                                                               

SQL> select count(*) from system.mystats;

SQL> select PNAME, PVAL2 from SYS.AUX_STATS$ where pname ='STATUS';

                                                                               

SQL> exec DBMS_STATS.IMPORT_SYSTEM_STATS ('mystats','DAY','SYSTEM');

BEGIN DBMS_STATS.IMPORT_SYSTEM_STATS ('mystats','DAY','SYSTEM'); END;

*
ERROR at line 1:
ORA-20003: Unable to import system statistics 
ORA-06512: at "SYS.DBMS_STATS", line 5437 
ORA-06512: at line 1


SQL> spool off

 

위의 DBMS_STATS.IMPORT_SYSTEM_STATS 수행 시 ORA-20003 error에 대해서는

이 문서 <Note:154601.1>을 참조하시기 바랍니다. User table로부터의 system 통계 정보를

Data Dictionary로 transfer할 때 ORA-20003 에러에 대한 해결방법입니다.

 

위 과정 전체를 JOB으로 등록하여 DBMS_JOB.SUBMIT으로 수행하는 방법은 아래

참고 노트에 나와 있습니다.


Reference Documents
-------------------
<Note:153761.1>
<Note:149560.1>

Posted by 1010
02.Oracle/DataBase2015. 5. 22. 14:17
반응형
테이블의 정확한 통계 수집을 위해 미리 알려줘야할 정보를 수집하는 DBMS함수

call dbms_stats.getther_table_stats('user',''table');


Posted by 1010
02.Oracle/DataBase2015. 5. 22. 14:14
반응형

출처 : http://blog.naver.com/darkturtle/50070269004


B. GATHER_SCHEMA_STATS Procedure

스키마 OWNER 의 모든OBJECT 에 대한 통계정보를 수집한다.

전체적으로 GATHER_TABLE_STATS 와 같고 아래 부분이 다르다.

스키마 유저만 지정 하면 GATHER OPTION  이 작동하여 모든 Object 의 정보가 수집 된다.



DBMS_STATS.GATHER_SCHEMA_STATS (

   ownname          VARCHAR2,

   estimate_percentNUMBER   DEFAULT to_estimate_percent_type

                 (get_param('ESTIMATE_PERCENT')),

   block_sample     BOOLEAN DEFAULT FALSE,

   method_opt       VARCHAR2 DEFAULT get_param('METHOD_OPT'),

   degree           NUMBER   DEFAULT

 to_degree_type(get_param('DEGREE')),

  granularity      VARCHAR2 DEFAULTGET_PARAM('GRANULARITY'),

   cascade          BOOLEAN  DEFAULT

to_cascade_type(get_param('CASCADE')),

   stattab          VARCHAR2 DEFAULT NULL,

   statid           VARCHAR2 DEFAULT NULL,

   options          VARCHAR2 DEFAULT 'GATHER',

   objlist          OUT      ObjectTab,

   statown          VARCHAR2 DEFAULT NULL,

   no_invalidate    BOOLEAN DEFAULT to_no_invalidate_type (

                                     get_param('NO_INVALIDATE')),

  force             BOOLEAN DEFAULT FALSE);

  

DBMS_STATS.GATHER_SCHEMA_STATS (

   ownname          VARCHAR2,

   estimate_percentNUMBER   DEFAULT to_estimate_percent_type

                                               (get_param('ESTIMATE_PERCENT')),

   block_sample     BOOLEAN DEFAULT FALSE,

   method_opt       VARCHAR2 DEFAULT get_param('METHOD_OPT'),

   degree           NUMBER  DEFAULT to_degree_type(get_param('DEGREE')),

  granularity      VARCHAR2 DEFAULTGET_PARAM('GRANULARITY'),

   cascade          BOOLEAN  DEFAULT to_cascade_type(get_param('CASCADE')),

   stattab          VARCHAR2 DEFAULT NULL,

   statid           VARCHAR2 DEFAULT NULL,

   options          VARCHAR2 DEFAULT 'GATHER',

   statown          VARCHAR2 DEFAULT NULL,

   no_invalidate    BOOLEAN DEFAULT to_no_invalidate_type (

                                     get_param('NO_INVALIDATE'),

   force            BOOLEAN DEFAULT FALSE);

 

 

 

Parameter

Description

Options

통계정보가 수집될 Object 에 대한 설명

GATHER : 스키마의 모든 Object 에 대해서 수집하라.

GATHER AUTO : 필요한 모든 정보를 자동으로 수집하라.

    GATHER AUTO 시에 설정 가능한 값은 ownname, stattab, statid, objlist,

     ,statown 만 가능하고 나머지 파라미터는 무시 된다.

GATHER STALE : *_tab_modification views 를 통해서 결정된 상태 변경이

     많은 테이블만 대상으로 통계정보를 수집한다.

GATHER EMPTY : 현재 통계정보가 존재하는 테이블만 대상으로

통계정보를 수집한다.

LIST AUTO : GATHER AUTO 옵션으로 진행될 Object list  Return 한다.

LIST STALE : GATHER STALE 옵션으로 진행될 Object list  Return 한다.

LIST EMPTY : GATHER EMPTY 옵션으로 진행될 Object list  Return 한다.

Objlist

통계정보가 상했거나없는 Object List


1. 통계정보가맛간 테이블 리스트 출력

<!--[if !supportLists]--><!--[endif]-->

SQL> setserveroutput on

SQL> declare

    mystaleobjs dbms_stats.objecttab;

    begin

    -- check whether there is any stale objects

   dbms_stats.gather_schema_stats(ownname=>'SCOTT', -

 options=>'LISTSTALE',objlist=>mystaleobjs);

    for i in 1 .. mystaleobjs.count loop

   dbms_output.put_line(mystaleobjs(i).objname);

    end loop;

    end;

  /


<!--[if !supportLists]-->2. 자동으로 통계정보수집 시에 대상 테이블 리스트 출력

SQL> setserveroutput on

SQL>declare

     mystaleobjs dbms_stats.objecttab;

    begin

    dbms_stats.gather_schema_stats(ownname=>‘SCOTT’,

options=>'LISTAUTO',objlist=>mystaleobjs);

    for i in 1 .. mystaleobjs.count loop

   dbms_output.put_line(mystaleobjs(i).objname);

    end loop;

   end ;

    /

PL/SQL proceduresuccessfully completed.

 

 

 

 -- 통계정보 생성이 필요한 테이블 리스트업 (DATA 이행등, STALE TABLE LIST 추출) 
SELECT *
FROM ALL_TAB_MODIFICATIONS
WHERE TABLE_OWNER IN ( 'SCOTT' )
and INSERTS > 100000 ;

-- 통계정보 생성 스크립트 작성 
SELECT 'exec dbms_stats.GATHER_TABLE_STATS(OWNNAME=>'''||table_owner||''',TABNAME=>'''||table_name||''');'
FROM ALL_TAB_MODIFICATIONS
WHERE TABLE_OWNER IN ( 'SCOTT' )
and INSERTS > 100000 ;


Posted by 1010
02.Oracle2015. 5. 22. 14:12
반응형

출처: http://egloos.zum.com/bosoa/v/1402860


다른 Database로의 통계정보 이관하기

 성능개선 혹은 Database Migration 작업등 운영계시스템에서 직접 작업하기가 곤란하여 별도 시스템을 구해 데이터이관작업을 수행하여야 하는 경우, 운영계시스템과는 별도의 개발 프로젝트를 진행해야 하고 이를 지원하기 위해 기존 운영계시스템의 SCHEMA를 동일한 환경에서 별도의 장비에 구성해야 하는 경우가 종종 발생한다.


 대량의 데이터를 보관하고 있는 운영계 시스템과 동일한 사이즈의 별도의 시스템을 구성하여 개발 및 테스트를 할 수만 있다면 가장 최상의 선택이겠지만, 여러 문제로 인해  도저히 운영계시스템의 H/W SPEC과 동일하게 가져 갈 수 없는 것이 보통 현장에서 겪는 현실이며, 심지어는 데이터는 한 건도 넣지
못하고, 빈 그릇만 가지고 테스트하거나 개발을 해야 하는 경우도 심심치 
않게 발생하곤 한다.

한가지 고민을 하게 된 것은 이관할 데이터량은 1/10 수준 혹은 그 이하이며,
데이터의 분포도 또한 운영계시스템과 동일하다 할 수 없는 상황에서도, 통계정보는 그대로 옮길 수 잇는 방법이 없는가 하는 것이다.

오라클을 사용하는 유저중에 많이 사용되지는 않지만 NewFeature안에 포함된
Package 및 utility중에서 위의 고민을 해결할 수 있는 방법이 제시되었는데,실제로Filed는 많이 활용되지 않는 것으로 판단된다.

본 문서에서는 데이터는 한건도 옮기지 않은 상황에서 UNIX 머신의 Schema를 
Windows 머신에 옮겨 테스트를 한 것이며, Optimizer Mode는 Cost Base이다.

자세한 테스트 시스템 환경은 다음과 같다.

FROM SERVER 
Machine : O/S - HP UNIX 
RDBMS Ver : Oracle 9.2.0.1
DBMS USER : SCOTT

TO SERVER
Machine  : O/S - Windows2000
RDBMS Ver : Oracle 9.2.0.4
DBMS USER : SCOTT

1. DBMS_STATS Package 를 이용한 통계정보 이관

dbms_stats PL/SQL Package(Oracle 8.1.5 이상부터) 는 CBO환경에서 통계정보를 생성하고 관리할 수 있도록 지원하는 Utility이다. 이것을 통해 수집,수정,조회,export,import및 통계정보의 삭제가 가능해진다. 또한 index와 table, 컬럼,및 partitions 및 모든 schema object에 대하여 이 유틸리티를 사용할 수 있다.

이 유틸리티의 자세한 사용방법은 Oracle Supplied PL/SQL Packages and Types Reference메뉴얼을 참고하기 바라며, 본 장에서는 통계정보의 export및 import를 사용하여 다른 시스템으로의 동일한 통계정보이관을 가능하도록 하는 방법에 대해 예시와 함께 간략하게 설명하고자 한다.

통계정보를 이관하기 위한 STEP을 간단히 설명하면

가. 통계정보를 임시로 담기 위한 임시 테이블 생성


나. 최신의 통계정보를 gather_table_stat package를 이용하여 수집하도록 한다.
    exec dbms_stats.gather_table_stats('SCOTT','TEST2',NULL,NULL,
                                        FALSE,'FOR ALL COLUMNS SIZE 74',
                                        NULL,'DEFAULT',TRUE,'STATS',NULL,NULL);

다. 이미 생성되어 있는 Object별 통계정보를 Data Dictionary에서 읽어 step가)   에서 생성한  임시 테이블에 INSERT


라. Oracle의 export/import를 이용하여 임시 Table에 저장된 data(통계정보)를 이관작업 수행

마. 이관된 Database내의 임시테이블에서 FROM database의 통계정보를 dbms_stats.import_table_stats Package를   이용하여 data dictionary로 이관작업을 수행.

  

가. 통계정보를 임시로 담기 위한 임시 테이블 생성 및 테스트 환경 생성

SQL> create table test2 
  2  (co1 varchar2(10) not null,
  3  co4 varchar2(10) not null,
  4  ch5 number not null);

Table created.

SQL> select count(*) from test2;

  COUNT(*)
----------
   1262240

SQL> analyze table test2 compute statistics;

Table analyzed.

SQL> select table_name,avg_row_len,num_rows from user_tables
  2* where table_name = 'TEST2'

TABLE_NAME                     AVG_ROW_LEN   NUM_ROWS
------------------------------ ----------- ----------
TEST2                                   16    1262240

 

analyze수행 후 나타는 실행 계획은 아래와 같다.

SQL> set autot trace exp

SQL> select co1 from test2
  2  where co1 like '0%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=499 Card=510510 Bytes=1531530)
   1    0   TABLE ACCESS (FULL) OF 'TEST2' (Cost=499 Card=510510 Bytes=1531530)


SQL> select co1 from test2
  2* where co1 = '001'


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=182 Card=54880 Bytes=164640)
   1    0   INDEX (RANGE SCAN) OF 'PK_TEST2' (UNIQUE) (Cost=182 Card=54880 Bytes=164640)

 


임시 통계정보를 담을 테이블 생성

SQL> exec dbms_stats.create_stat_table('SCOTT','STATS');
     EXEC DBMS_STATS.CREATE_STAT_TABLE('계정명','생성할 TABLE명');

PL/SQL procedure successfully completed.

SQL> select * from tab where tname = 'STATS';

TNAME                          TABTYPE  CLUSTERID
------------------------------ ------- ----------
STATS                          TABLE

나. 최신의 통계정보를 gather_table_stat package를 이용하여 수집

SQL> exec dbms_stats.gather_table_stats('SCOTT',
                                                             'TEST2',
                                                             NULL,
                                                             NULL,
                                                             FALSE,
                                                             'FOR ALL COLUMNS SIZE AUTO',
                                                             NULL, 'DEFAULT',
                                                             TRUE,
                                                             'STATS',
                                                             NULL,
                                                             NULL);
PL/SQL procedure successfully completed.

혹은  
   
SQL> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',
            stattab=>'STATS',
            tabname=>'TEST2',
            method_opt=>'FOR ALL INDEXED COLUMNS SIZE 74',
            cascade=>TRUE);
 

전체 SCHEMA에 대한 통계정보를 수집하려 한다면 아래와 같이 "gather_schema_stats"를 수행하면 된다.

SQL> EXECUTE dbms_stats.gather_schema_stats(
                        ownname => 'Username',
                        method_opt => 'FOR ALL INDEXED COLUMNS SIZE AUTO',
       

DBMS_STATS.EXPORT_SCHEMA_STATS (
   ownname VARCHAR2,
   stattab VARCHAR2, 
   statid  VARCHAR2 DEFAULT NULL,
   statown VARCHAR2 DEFAULT NULL);

ownname  Name of the schema. 
stattab  User stat table identifier describing where to store the statistics. 
statid   Identifier (optional) to associate with these statistics within stattab. 
statown  Schema containing stattab (if different than ownname).


다. 이미 생성되어 있는 Object별 통계정보를 Data Dictionary에서 읽어 step 가)에서 생성한 임시 테이블에 INSERT

SQL> exec dbms_stats.export_table_stats(ownname=>'SCOTT',stattab=>'STATS',tabname=>'TEST2',cascade=>TRUE);

PL/SQL procedure successfully completed.

SQL>

SQL> select count(*) from stats;

  COUNT(*)
----------
       108

 

SQL> exec dbms_stats.create_stat_table('SCOTT','STATS');

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> desc stats
 이름                                      널?      유형
 ----------------------------------------- -------- ---------------------
 STATID                                             VARCHAR2(30)
 TYPE                                               CHAR(1)
 VERSION                                            NUMBER
 FLAGS                                              NUMBER
 C1                                                 VARCHAR2(30)
 C2                                                 VARCHAR2(30)
 C3                                                 VARCHAR2(30)
 C4                                                 VARCHAR2(30)
 C5                                                 VARCHAR2(30)
 N1                                                 NUMBER
 N2                                                 NUMBER
 N3                                                 NUMBER
 N4                                                 NUMBER
 N5                                                 NUMBER
 N6                                                 NUMBER
 N7                                                 NUMBER
 N8                                                 NUMBER
 N9                                                 NUMBER
 N10                                                NUMBER
 N11                                                NUMBER
 N12                                                NUMBER
 D1                                                 DATE
 R1                                                 RAW(32)
 R2                                                 RAW(32)
 CH1                                                VARCHAR2(1000)

 

라. Oracle의 export/import를 이용하여 임시 Table에 저장된 data(통계정보)를 이관작업 수행

[encuxdb:/oracle/app/oracle/product/9.2.0]exp scott/tiger file=stats.dmp tables=stats


Export: Release 9.2.0.1.0 - Production on Thu Apr 8 10:47:32 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.


Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in KO16KSC5601 character set and AL16UTF16 NCHAR character set

About to export specified tables via Conventional Path ...
. . exporting table                          STATS        108 rows exported
Export terminated successfully without warnings.

 

이 이후 작업은 이관할 시스템에서 작업하도록 한다.
TEST2라는 테이블과 임시 통계자료를 보관할 STATS 테이블은 만들어져 있는 상황이며,
TEST2라는 테이블에는 한건도 없는 상황이다.

마. 이관된 Database내의 임시테이블에서 FROM database의 통계정보를 dbms_stats.import_table_stats Package를  이용하여 data dictionary로 이관작업을 수행.


D:>imp scott/tiger file=stats.dmp log=stats_imp.log commit=y ignore=y full=y

다음에 접속됨: Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

엑스포트 파일은 규정 경로를 거쳐 EXPORT:V09.02.00 에 의해 생성되었습니다
KO16MSWIN949 문자집합과 AL16UTF16 NCHAR 문자 집합에 임포트가 완성되었습니다
KO16KSC5601 문자 집합을 사용해서 클라이언트를 엑스포트합니다 (문자집합 변환이 가능합니다)
. SCOTT 객체를 SCOTT(으)로 임포트하는 중입니다
. . 테이블                        "STATS"(를)을 임포트 중        108 행이 임포트되었습니다
임포트가 경고 없이 정상적으로 종료되었습니다.

 

SQL> select count(*) from stats;

  COUNT(*)
----------
       108

SQL> select count(*) from test2;

  COUNT(*)
----------
         0
         
SQL> set autot trace exp

SQL> select co1 from test2
  2  where co1 like '0%';

Execution Plan
--------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'PK_TEST2' (UNIQUE)

 

SQL> select co1 from test2
  2  where co1 = '001';

Execution Plan
-----------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   INDEX (RANGE SCAN) OF 'PK_TEST2' (UNIQUE)

 

위 제시된 실행계획에서 보는 바와 같이 Test2 Table에는 데이터가 한건도 없으며,
기존에 analyze작업을 수행한 적이 없는 상황이다.

 

마. 이관된 Database내의 임시테이블에서 FROM database의 통계정보를 dbms_stats.import_table_stats Package를
    이용하여 data dictionary로 이관작업을 수행.

SQL> exec dbms_stats.import_table_stats(ownname=>'SCOTT',tabname=>'TEST2',stattab=>'STATS',cascade=>TRUE);

PL/SQL 처리가 정상적으로 완료되었습니다.


SQL> select count(*) from test2;

  COUNT(*)
----------
         0

SQL> set autot trace exp
SQL> select co1 from test2
  2  where co1 like '0%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=317 Card=548801 Bytes=2195204)
   1    0   TABLE ACCESS (FULL) OF 'TEST2' (Cost=317 Card=548801 Bytes=2195204)


SQL> select co1 from test2
  2  where co1 = '001';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=182 Card=54880 Bytes=219520)
   1    0   INDEX (RANGE SCAN) OF 'PK_TEST2' (UNIQUE) (Cost=182 Card=54880 Bytes=219520)


위 예에서 보는 바와 같이 stats테이블에 수집된 test2 table에 대한 통계정보가
import_table_stats package를 통해 data dictionary에 저장되고, 데이터 건수가 전혀 없는 상황에서도
FROM SERVER와 동일한 실행계획과 COST를 볼 수 있다.

 

 


2. Export & Import Utility를 이용한 통계정보 이관


DBMS Migration 및 기타 data backup의 목적으로 사용하는 Export & Import의 Option중에 잘 사용하지 않는 statistics라는 Option을 사용하면 1번과 같이 여러 STEP을 거쳐 작업하지 않아도 통계정보를 그대로 이관할 수 있다.
물론 이런 기능을 사용하는데는 몇가지 제약조건이 있는데, 오라클 Version이 9i이상이어야 하며(export시 statistics는 현재 8i에서도 제공하지만, import시에는 제공되지 않고 있음), 동일한 Character Set을 가지고 있어야하는 export & import 제약조건과 동일하다.


가. Export하기


[encuxdb:/oracle/app/oracle/product/9.2.0]exp scott/tiger file=test2.dmp log=test2.log rows=n statistics=compute tables=test2

rows=n 파라메터를 이용하여 test2라는 테이블에 있는 row는 전혀 추출하지 않고 export를 수행하였다.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
Export done in KO16KSC5601 character set and AL16UTF16 NCHAR character set
Note: table data (rows) will not be exported

About to export specified tables via Conventional Path ...
. . exporting table                          TEST2
Export terminated successfully without warnings.

 

나. Import하기
D:>imp scott/tiger file=test2.dmp log=imp.log commit=y ignore=y full=y statistics=always

다음에 접속됨: Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production

엑스포트 파일은 규정 경로를 거쳐 EXPORT:V09.02.00 에 의해 생성되었습니다
KO16MSWIN949 문자집합과 AL16UTF16 NCHAR 문자 집합에 임포트가 완성되었습니다
KO16KSC5601 문자 집합을 사용해서 클라이언트를 엑스포트합니다 (문자집합 변환이 가능합니다)
. SCOTT 객체를 SCOTT(으)로 임포트하는 중입니다
임포트가 경고 없이 정상적으로 종료되었습니다.


다. Import후 실행계획 확인

SQL> set autot trace exp
SQL> select co1 from test2
  2  where co1 like '0%';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=317 Card=548801 Bytes=2195204)
   1    0   TABLE ACCESS (FULL) OF 'TEST2' (Cost=317 Card=548801 Bytes=2195204)
   
   
SQL> select co1 from test2
  2  where co1 = '001';

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=182 Card=54880 Bytes=219520)
   1    0   INDEX (RANGE SCAN) OF 'PK_TEST2' (UNIQUE) (Cost=182 Card=54880 Bytes=219520)
  


기타 사용자 계정이 달라질 경우에는 FROMUSER, TOUSER 파라메터를 사용하여 Import를 수행하도록한다.

 

이와같이 시스템상황에 따라서는 전혀 옮겨질 수 없는 대형 시스템 SPEC이라 하더라도 빈 Table들과 그들의 통계정보를 얼마든지 추출하여 새로운 시스템을 구축할 수 있고, 이를 이용하면 운영계시스템에서 작업하는 동일한 수준의 작업을 수행할 수 있음을 알 수 있다.

Posted by 1010
02.Oracle2015. 1. 20. 16:26
반응형
출처 : http://blog.nachal.com/442
[ORACLE/SQL] 셀렉트 후 업데이트, 수정 가능한 조인 뷰, SELECT-UPDATE, Modifiable Join View

멋도모르고 SELECT-UPDATE문을 찾아서 사용하고있었는데,

엄청난 사실 - IN절에 사용한 컬럼명이 키가 아닐경우 UPDATE가 정상적으로 이뤄지지 않는다
는거였습니다. (엉뚱한 데이타가 업데이트되고 돼야할 레코드는 업데이트 안되고,,,)
물론 해당 테이블의 키가 컬럼 한개가 아니라면 갯수도 맞아야 하죠.

그래서 이걸 루프를 돌려야하나 말아야하나 하다가 알아낸게 '수정 가능한 조인 뷰' 입니다.

UPDATE의 테이블 대신에 수정할만큼의 데이타만 들고와서 SET을 하는 쿼리입니다.


기존 쿼리 (SELECT-UPDATE)

UPDATE TRNSAPLC T1
SET
T1.RESULT_CODE='2',
T1.REASON='넌 불합격이야'
WHERE T1.SEQ_NO IN (
SELECT T.SEQ_NO
FROM
TRNSAPLC T,
ADMTBASE T2,
REG TS
WHERE 1=1
AND T.SENO=T2.SENO
AND T.STU_NO=TS.STU_NO(+)
AND T.APPLY_DATE IS NOT NULL
AND T.YEAR='2010'
AND T.APLCDGE='1'
)
;



새로운 쿼리 (Modifiable Join View)

UPDATE (
SELECT T.RESULT_CODE , T.REASON 
FROM
TRNSAPLC T,
ADMTBASE T2,
REG TS 
WHERE 1=1
AND T.SENO=T2.SENO
AND T.STU_NO=TS.STU_NO(+)
AND T.APPLY_DATE IS NOT NULL
AND T.YEAR='2010'
AND T.APLCDGE='1'
) T1
SET
T1.RESULT_CODE='2',
T1.REASON='넌 불합격이야'
;


Posted by 1010
02.Oracle/DataBase2014. 11. 21. 19:21
반응형

출처 : http://tip.daum.net/question/62996518


ORA-06508 PL/SQL: could not find program unit being called ~

 

<< 환경 >>

인터페이스 모듈을 프로시져로 작성하고 있습니다.

Oracle9i Enterprise Edition Release 9.2.0.8.0 - 64bit Production

 

인스턴스 instance-1 이 있구요.

인스턴스 안에 스키마 user-1 , user-2가 있습니다.

 

user-2에서 프로시져를 작성했구요 프로시져 안에서 user-1 에서 만든 패키지 pkg_1를 콜합니다.

pkg_1은 user-2의 다른 pkg_2를 콜합니다.

 

( user_2.프로시져 -> user_1.pkg_1 -> user_2.pkg_2 ) 이런 식입니다.

 

<< 에러현상 재현>>

여러분들이 많이 쓰시는 Toad나 Orange, Develper 등에서 user_2로 login해서 작업합니다.

 

작업1] session-1st를 생성 -> 1st session에서 프로시져를 실행했을때 문제없이 돌아갑니다.

 

작업2] session-2nd를 생성 -> 2nd session에서 프로시져를 실행했을때 마찬가지로 문제없이 돌아갑니다.

 

작업3] session-1st에서 프로지져안에서 콜하는 pkg_2를 컴파일 합니다. (user_2는 권한가지고있음)

        (이때 pkg_1은 invalid 상태로 변합니다.)

 

작업4] session-2nd에서 해당 프로시져를 콜하면 위와 같은 에러메시지가 나옵니다.

 

작업5] session-1st에서 invalid인 pkg_1을 재컴파일하지 않은 상태에서 프로지져실행 -> 잘돌아갑니다. (이때 invalid한 pkg_1은 valid한 상태로 바뀝니다.)

 

작업6] session-2nd에서 프로시져를 실행 -> 같은 에러가 나옵니다.

 

작업7] session-3rd를 생성 -> 3rd session에서 프로시져를 실행했을때 문제없이 돌아갑니다.

 

작업8] session-2nd에서 프로시져를 실행 -> 같은 에러가 나옵니다

 

==> 정리하면 pkg_2를 컴파일하는 session은 전혀 문제가 없습니다.

==> session이 유지되고 있는 상태에서 다른 session이 자신이 access하는 pkg_2를 컴파일 하면 해당 session은 에러를 발생시키지만...새로 맺는 session은 에러를 발생시키지 않습니다.

 

이 현상을 파악하는데....1개월이 걸렸습니다.

 

그런데..해결 방안이 떠오르질 않네요..

 

좀 더 구체적으로 말씀드리자면...session-2nd는 EAI SERVER에서 Connection Pool이라고 생각하시면 됩니다. 그러니...새로운 .session을 맺으라는 얘기는 db를 restart하라는 얘기와 같습니다.

 

[[제 생각]]

제가 알기론 db가 구동될때...ORACLE SUB_PROGRAM(fn,sp,pkg등)정보를 Dictionary cache에 올리는 걸로 알고 있는데...이쪽에 원인이 있다고 생각하는데..(동기화등등 ) 암튼 지금..넘 막연하네요.

 

조치는 해야 하는데..하루하루가...죽을맛입니다...

 

그럼 부탁드리구요..좋은 하루 되세요...



-----------------------------



pl/sql의 hot deploy가 안되는 경우입니다....

 

 1. dependency가 아주 깊이 있는 경우 invalid된 package가 자동 compile이 안되는 경우가 있읍니다.

 2. 수정에 의해 invalid된 package안에서 package level의 변수(전역변수)를 사용한 경우

    발생할수 있읍니다.

 

아래의 오라클 문서를 참고하세요.

 

ORA-04068 Errors from User-Written and Oracle Packages
 Doc ID:Note:106206.1Type:BULLETIN
 Last Revision Date:14-NOV-2006Status:PUBLISHED


Problem Description
-------------------

You receive the following error from user-written and Oracle packages:

   ORA-04068  existing state of packages%s%s%s has been discarded


Causes of ORA-4068 Errors
-------------------------

1) A dependent object was altered through a DDL statement.

   When a package is compiled, all copies in the shared pool are
   flagged as invalid.  The next invocation of the package sees
   this flag set, and goes to get a new copy.  

   If the package is now invalid, cannot compile, or relied on 
   a package state (i.e., package level variables), then this error 
   occurs because the current copy of the package is no longer valid 
   and must be thrown out.

2) The package was changed or recompiled (both DDL) and the package
   being used contains package level variables.

   Same as above.  When a package dependency is altered through
   DDL statements (DROP, CREATE, ALTER, ...), this package is
   flagged as invalid through cascade invalidation.

3) A package relied on another package that had no body, and during
   execution, the call failed.

   When a package is compiled, it only looks for the specification.  
   During execution, it calls a non-existent routine and throws an 
   error.  This error then invalidates the package.

   Another variation is if the procedure being called is not defined
   in the package body and possibly as a standalone routine.

4) A remote dependent object has been altered through a DDL statement.
   This can occur between database instances or from Forms or Reports
   to a database instance.

   The default remote dependency model uses the Timestamp model, and when 
   an execution of a procedure takes place, the remote object's timestamp 
   is validated, thus forcing invalidation on the local package.

   To check for these situations, several SQL statements can be run:

  a. To check the package's last compile:

     SELECT object_name, object_type, owner, status, last_ddl_time FROM
       dba_objects WHERE object_name = '<PACKAGE NAME>';

     For example:

     SQL> SELECT object_name, object_type, owner, status, last_ddl_time FROM
       2    dba_objects WHERE object_name = 'DBMS_SQL';

     OBJECT_NAME
     ------------------------------------------------------------------------
     OBJECT_TYPE   OWNER                          STATUS  LAST_DDL_
     ------------- ------------------------------ ------- ---------
     DBMS_SQL
     PACKAGE       SYS                            VALID   13-JUL-99

     DBMS_SQL
     PACKAGE BODY  SYS                            VALID   13-JUL-99

     DBMS_SQL
     SYNONYM       PUBLIC                         VALID   13-JUL-99

     SQL> 

  b. To check the dependent objects last alteration:

     SELECT object_name, object_type, owner, status, last_ddl_time FROM
       dba_objects WHERE ( object_name, object_type ) IN ( SELECT
       referenced_name, referenced_type FROM dba_dependencies WHERE name =
       '<PACKAGE NAME>' );

     For example:

     SQL> SELECT object_name, object_type, owner, status, last_ddl_time FROM
       2    dba_objects WHERE ( object_name, object_type ) IN ( SELECT
       3    referenced_name, referenced_type FROM dba_dependencies WHERE name =
       4    'DBMS_SQL' );

     OBJECT_NAME
     -----------------------------------------------------------------------------
     OBJECT_TYPE   OWNER                          STATUS  LAST_DDL_
     ------------- ------------------------------ ------- ---------
     DBMS_SQL
     PACKAGE       SYS                            VALID   13-JUL-99

     DBMS_SYS_SQL
     PACKAGE       SYS                            VALID   13-JUL-99

     STANDARD
     PACKAGE       SYS                            VALID   13-JUL-99


     SQL> 

  c. To check for existing errors on package:

     SELECT name, type, text FROM dba_errors WHERE name = '<PACKAGE NAME>';

     For example:

     SQL> SELECT name, type, text FROM dba_errors WHERE name = 'DBMS_SQL';

     no rows selected

     SQL> 


Solutions for ORA-4068 Errors
-----------------------------

1) Simply re-execute the package.

   For example:

   Session 1:  Create the package and body for package p_pack:

   SQL> create or replace package p_pack as
     2      p_var varchar2(1);
     3      procedure p;
     4  end p_pack;
     5  /

   Package created.

   SQL> create or replace package body p_pack as
     2      procedure p is
     3          begin
     4              p_var := 1;
     5          end;
     6  end p_pack;
     7  /

   Package body created.

   SQL> 

   Session 2:  Execute the package:

   SQL> exec p_pack.p

   PL/SQL procedure successfully completed.

   SQL> 

   Session 1:  Recreate the package and body:

   SQL> create or replace package p_pack as
     2      p_var varchar2(1);
     3      procedure p;
     4  end p_pack;
     5  /

   Package created.

   SQL> create or replace package body p_pack as
     2      procedure p is
     3          begin
     4              p_var := 1;
     5          end;
     6  end p_pack;
     7  /

   Package body created.

   SQL> 

   Session 2:  Re-execute the package:

   SQL> exec p_pack.p
   begin p_pack.p; end;

   *
   ERROR at line 1:
   ORA-04068: existing state of packages has been discarded
   ORA-04061: existing state of package "SCOTT.P_PACK" has been invalidated
   ORA-04065: not executed, altered or dropped package "SCOTT.P_PACK"
   ORA-06508: PL/SQL: could not find program unit being called
   ORA-06512: at line 1


   SQL> exec p_pack.p

   PL/SQL procedure successfully completed.

   SQL> 

   As background, when the ORA-4068 is raised, ORACLE will throw away all 
   existing instantiations of the package.  When the package (more properly, 
   the subprogram referring to the package) is re-executed, ORACLE will 
   re-instantiate the package automatically (if possible), which 
   typically will succeed, and re-execution of the subprogram will succeed.

   An important proviso is that the ORA-4068 error must be unhandled on exit 
   from the subprogram in order for this solution to work.  It's only when an 
   _unhandled_ ORA-4068 is returned by PL/SQL to ORACLE that the needed 
   deinstantiations take place.  
   (The ORA-4068 may be handled in the subprogram and various actions 
   taken in the handler, but the error must be reraised in order for it to be 
   unhandled on exit from PL/SQL to get the desired deinstantiations.)


2) Attempt to recompile the package by using the ALTER PACKAGE command.

   For example:

   SQL> ALTER PACKAGE DBMS_SQL COMPILE;

   Package altered.

   SQL> 

3) Verify that proper execute permissions have been provided.  In PL/SQL 
   stored program units, roles are disabled prior to the release of Oracle 
   8i.  Oracle 8i definers rights follow the previous release model of 
   requiring explicit permission to the object.  

   In Oracle 8i, if invoker's rights are set on the routine, then execution 
   is done with roles enabled, so permission could be granted explicitly to 
   the schema executing or through a role.  For additional information,
   refer to Note 162489.1 entitled "Invokers Rights Procedure Executed by Definers Rights Procedures".

 


   For example:

   SQL> SELECT owner, table_name, privilege FROM dba_tab_privs WHERE
     2    table_name = 'DBMS_SQL' AND ( grantee = 'SCOTT' OR grantee = 'PUBLIC' );

   OWNER                          TABLE_NAME
   ------------------------------ ------------------------------
   PRIVILEGE
   ----------------------------------------
   SYS                            DBMS_SQL
   EXECUTE


   SQL> 

4) If errors exist (check DBA_ERRORS or USER_ERRORS views with above
   query), then take the appropriate action to correct the errors.  

   If the package is a system package that comes with the Oracle server, 
   the scripts are located in $ORACLE_HOME/rdbms/admin.  Most packages have
   their own .sql and .plb script to build the specification and body (see
   below for names).  

   System packages that come with the Oracle server, as well as other Oracle 
   products, typically need to be owned by a particular schema.  In the case 
   of the Oracle server DBMS packages, these need to be owned by SYS.  If these
   packages are not owned by SYS, some packages start getting 'ORA-6509 PL/SQL 
   ICD vector missing for this package' errors.

5) If duplicate SYS owned objects exist, clean them up.  Refer to 
   Note 1030426.6, entitled "HOW TO CLEAN UP DUPLICATE OBJECTS OWNED BY 
   SYS AND SYSTEM SCHEMA", for information on this.


$ORACLE_HOME/rdbms/admin Packages
---------------------------------
System Package Name   Package Spc   Package Bdy   Owner
-------------------   -----------   -----------   -----
DBMS_ALERT            dbmsalrt.sql  prvtalrt.plb  SYS
DBMS_APPLICATION_INFO dbmsapin.sql  prvtapin.plb  SYS
DBMS_AQ               dbmsaq.plb    prvtaq.plb    SYS
DBMS_AQADM            dbmsaqad.sql  prvtaqad.plb  SYS
DBMS_CRYPTO_TOOLKIT   dbmsoctk.sql  prvtoctk.plb  SYS
DBMS_DDL              dbmsutil.sql  prvtutil.plb  SYS
DBMS_DEBUG            dbmspb.sql    prvtpb.plb    SYS
DBMS_DEFER            dbmsdefr.sql  prvtdefr.plb  SYS
DBMS_DEFER_QUERY      dbmsdefr.sql  prvtdefr.plb  SYS
DBMS_DEFER_SYS        dbmsdefr.sql  prvtdefr.plb  SYS
DBMS_DESCRIBE         dbmsdesc.sql  prvtdesc.plb  SYS
DBMS_DISTRIBUTED_TRUST_ADMIN
                      dbmstrst.sql  prvttrst.plb  SYS
DBMS_HS               dbmshs.sql    prvths.plb    SYS
DBMS_IOT              dbmsiotc.sql  prvtiotc.plb  SYS
DBMS_JOB              dbmsjob.sql   prvtjob.plb   SYS
DBMS_LOB              dbmslob.sql   prvtlob.plb   SYS
DBMS_LOCK             dbmslock.sql  prvtlock.plb  SYS
DBMS_LOGMNR           dbmslm.sql    prvtlm.plb    SYS
DBMS_LOGMNR_D         dbmslmd.sql   dbmslmd.sql   SYS
DBMS_OFFLINE_OG       dbmsofln.sql  prvtofln.plb  SYS
DBMS_OFFLINE_SNAPSHOT dbmsofsn.sql  prvtofsn.plb  SYS
DBMS_ORACLE_TRACE_AGENT
                      dbmsotrc.sql  prvtotrc.plb  SYS
DBMS_ORACLE_TRACE_USER
                      dbmsotrc.sql  prvtotrc.plb  SYS
DBMS_OUTPUT           dbmsotpt.sql  prvtotpt.plb  SYS
DBMS_PCLXUTIL         dbmsutil.sql  prvtutil.plb  SYS
DBMS_PIPE             dbmspipe.sql  prvtpipe.sql  SYS
DBMS_RANDOM           dbmsrand.sql  dbmsrand.sql  SYS
DBMS_RECTIFIER_DIFF   dbmsrctf.sql  prvtrctf.plb  SYS
DBMS_REFRESH          dbmssnap.sql  prvtsnap.plb  SYS
DBMS_REPAIR           dbmsrpr.sql   prvtrpr.plb   SYS
DBMS_REPCAT           dbmshrep.sql  prvtbrep.plb  SYS
DBMS_REPCAT_ADMIN     prvthdmn.plb  prvtbdmn.plb  SYS
DBMS_REPCAT_INSTANTIATE
                      dbmsrint.sql  prvtbrnt.plb  SYS
DBMS_REPCAT_RGT       dbmsrgt.sql   prvtbrgt.plb  SYS
DBMS_REPUTIL          dbms_gen.sql  prvtgen.plb   SYS
DBMS_RESOURCE_MANAGER dbmsrmad.sql  prvtrmad.plb  SYS
DBMS_RESOURCE_MANAGER_PRIVS
                      dbmsrmpr.sql  prvtrmpr.plb  SYS
DBMS_RLS              dbmsrlsa.sql  prvtrlsa.plb  SYS
DBMS_ROWID            dbmsutil.sql  prvtutil.plb  SYS
DBMS_SESSION          dbmsutil.sql  prvtutil.plb  SYS
DBMS_SHARED_POOL      dbmspool.sql  prvtpool.plb  SYS
DBMS_SNAPSHOT         dbmssnap.sql  prvtsnap.plb  SYS
DBMS_SPACE            dbmsutil.sql  prvtutil.plb  SYS
DBMS_SPACE_ADMIN      dbmsspc.sql   prvtspad.plb  SYS
DBMS_SQL              dbmssql.sql   prvtsql.plb   SYS
DBMS_STATS            dbmsstat.sql  prvtstat.plb  SYS
DBMS_TRACE            dbmspbt.sql   prvtpbt.plb   SYS
DBMS_TRANSACTION      dbmsutil.sql  prvtutil.plb  SYS
DBMS_UTILITY          dbmsutil.sql  prvtutil.plb  SYS
OUTLN_PKG             dbmsol.sql    prvtol.plb    SYS
UTL_COLL              utlcoll.sql   prvtcoll.plb  SYS
UTL_FILE              utlfile.sql   prvtfile.plb  SYS
UTL_HTTP              utlhttp.sql   prvthttp.plb  SYS
UTL_RAW               utlraw.sql    prvtrawb.plb  SYS
UTL_REF               utlref.sql    prvtref.plb   SYS


References
----------

"Oracle7 Server Application Developer's Guide", (A32536-1)

"Oracle8 Server Application Developer's Guide", (A54642-01)

"Oracle8i Application Developer's Guide - Fundamentals Release 8.1.5", 
   (A68003-01)


Related Articles
----------------

Note 19857.1 OERR:  ORA 4068  "existing state of packages%s%s%s has been 
               discarded"

Note 19854.1 OERR:  ORA 4065  "not executed, altered or dropped %s"

Note 19850.1 OERR:  ORA 4061  "existing state of %s has been invalidated"

Note 20065.1 OERR:  ORA 6508  "PL/SQL: could not find program unit being 
               called"

Note 1012129.102 ORA-4068,4067,6508 When Executing a Procedure Using PL/SQL

Note 117118.1  Errors Running or Compiling DBMS_RANDOM or DBMS_CRYPTO_TOOLKIT

Note 1030426.6 HOW TO CLEAN UP DUPLICATE OBJECTS OWNED BY SYS AND SYSTEM 
                 SCHEMA

Note 1012129.102  ORA-4068,4067,6508 WHEN EXECUTING A PROCEDURE USING PLSQL


Related Errors
--------------

ORA-4061
ORA-4063
ORA-4065
ORA-4067 
ORA-4068
ORA-4088
ORA-6508
ORA-6509
ORA-6568



ORA-6512


Posted by 1010
02.Oracle/DataBase2014. 11. 14. 20:23
반응형

출처 : http://blog.beany.co.kr/archives/485

예전에 테스트로 만들어 본 Oracle CURD Package Sample

  • Package 정의
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
39
40
41
42
43
44
45
46
47
CREATE OR REPLACE PACKAGE SP_CMM_DEVL_0170
AS
TYPE REF_TYPE IS REF CURSOR;
 
-- 다중 반환
PROCEDURE MULTI_RETURN (
                p_search_str IN  VARCHAR2
               ,p_return_1   OUT REF_TYPE
               ,p_return_2   OUT VARCHAR2
              );
 
-- 목록
PROCEDURE LST (
                p_search_str IN  VARCHAR2
               ,p_return     OUT REF_TYPE
              );
 
-- 입력
PROCEDURE INS (
                p_word_kor  IN VARCHAR2
               ,p_abbr_nm   IN VARCHAR2
               ,p_word_eng  IN VARCHAR2
               ,p_word_desc IN VARCHAR2
               ,p_reg_id    IN VARCHAR2
               ,p_reg_ip    IN VARCHAR2
               ,p_reg_dt    IN VARCHAR2
              );
 
-- 수정
PROCEDURE UPD (
                p_seq       IN NUMBER
               ,p_word_kor  IN VARCHAR2
               ,p_abbr_nm   IN VARCHAR2
               ,p_word_eng  IN VARCHAR2
               ,p_word_desc IN VARCHAR2
               ,p_upd_id    IN VARCHAR2
               ,p_upd_ip    IN VARCHAR2
               ,p_upd_dt    IN VARCHAR2
              );
 
-- 삭제
PROCEDURE DEL (
                p_seq IN NUMBER
              );
 
END SP_CMM_DEVL_0170;
/
  • Package Body 구현
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
CREATE OR REPLACE PACKAGE BODY SP_CMM_DEVL_0170
IS
 
-- 다중 반환
PROCEDURE MULTI_RETURN (
                p_search_str IN  VARCHAR2
               ,p_return_1   OUT REF_TYPE
               ,p_return_2   OUT VARCHAR2
)
IS
    v_query   VARCHAR2(1000);
    v_message VARCHAR2(2000);
BEGIN
     
    v_query := 'SELECT  SEQ
                       ,WORD_KOR
                       ,ABBR_NM
                       ,WORD_ENG
                       ,WORD_DESC
                       ,REG_ID
                       ,REG_IP
                       ,REG_DT
                       ,UPD_ID
                       ,UPD_IP
                       ,UPD_DT
                FROM   SAMPLE_DIC
               WHERE  1 = 1';
 
    IF (LENGTH(p_search_str) > 0) THEN
        v_query := v_query || ' AND WORD_KOR LIKE ''' || p_search_str || '%''';
    END IF;
     
    v_query := v_query || ' ORDER BY SEQ';
     
    OPEN p_return_1 FOR v_query;
     
    v_message  := 'Procedure 반환값!!';
    p_return_2 := v_message;
END MULTI_RETURN;
 
-- 목록
PROCEDURE LST (
                p_search_str IN  VARCHAR2
               ,p_return     OUT REF_TYPE
)
IS
    v_query VARCHAR2(1000);
BEGIN
     
    v_query := 'SELECT  SEQ
                       ,WORD_KOR
                       ,ABBR_NM
                       ,WORD_ENG
                       ,WORD_DESC
                       ,REG_ID
                       ,REG_IP
                       ,REG_DT
                       ,UPD_ID
                       ,UPD_IP
                       ,UPD_DT
                FROM   SAMPLE_DIC
               WHERE  1 = 1';
 
    IF (LENGTH(p_search_str) > 0) THEN
        v_query := v_query || ' AND WORD_KOR LIKE ''' || p_search_str || '%''';
    END IF;
     
    v_query := v_query || ' ORDER BY SEQ';
     
    OPEN p_return FOR v_query;
END LST;
 
-- 입력
PROCEDURE INS (
                p_word_kor  IN VARCHAR2
               ,p_abbr_nm   IN VARCHAR2
               ,p_word_eng  IN VARCHAR2
               ,p_word_desc IN VARCHAR2
               ,p_reg_id    IN VARCHAR2
               ,p_reg_ip    IN VARCHAR2
               ,p_reg_dt    IN VARCHAR2
)
IS
BEGIN
    INSERT INTO SAMPLE_DIC (
                             SEQ
                            ,WORD_KOR
                            ,ABBR_NM
                            ,WORD_ENG
                            ,WORD_DESC
                            ,REG_ID
                            ,REG_IP
                            ,REG_DT
                           )
    VALUES (
             SAMPLE_DIC_SEQ.NEXTVAL
            ,p_word_kor
            ,p_abbr_nm
            ,p_word_eng
            ,p_word_desc
            ,p_reg_id
            ,p_reg_ip
            ,p_reg_dt
           );
END INS;
 
-- 수정
PROCEDURE UPD (
                p_seq       IN NUMBER
               ,p_word_kor  IN VARCHAR2
               ,p_abbr_nm   IN VARCHAR2
               ,p_word_eng  IN VARCHAR2
               ,p_word_desc IN VARCHAR2
               ,p_upd_id    IN VARCHAR2
               ,p_upd_ip    IN VARCHAR2
               ,p_upd_dt    IN VARCHAR2
)
IS
BEGIN
    UPDATE  SAMPLE_DIC
    SET     WORD_KOR  = p_word_kor
           ,ABBR_NM   = p_abbr_nm
           ,WORD_ENG  = p_word_eng
           ,WORD_DESC = p_word_desc
           ,UPD_ID    = p_upd_id
           ,UPD_IP    = p_upd_ip
           ,UPD_DT    = p_upd_dt
    WHERE  SEQ        = p_seq;
END UPD;
 
-- 삭제
PROCEDURE DEL (
                p_seq IN NUMBER
)
IS
BEGIN
    DELETE
    FROM SAMPLE_DIC
    WHERE SEQ = p_seq;
END DEL;
 
END SP_CMM_DEVL_0170;
/


Posted by 1010
02.Oracle/DataBase2014. 11. 5. 18:46
반응형

64Bit OS에서 오렌지 설치 후  TNS 문제로 접속이 않되는 경우

설치 위치를 변경해 보는것이 좋다 C:\Program Files (x86) 아래가 아닌곳으로

 

ex) C:\orange



Posted by 1010
02.Oracle2014. 7. 29. 17:57
반응형

Oracle JDBC의 CachedRowSet Implement Bug

CachedRowSet을 사용하는 경우 Oracle JDBC의 CachedRowSet Implementation Bug 때문에 더 이상 고생하는 개발자가 없기를 바라며 이 글을 작성한다.

결론부터 말하자면 Oracle의 JDBC Driver가 CachedRowSet을 Implement한 Class의 Method 중 Date Type에 대한 Bug로 인해 CachedRowSet을 이용한 Code에서 오류가 발생될 수 있다는 것이다. 그리고 이는 Patch된 최신 Version인(현재 시점) 'ojdbc5.jar' 이상을 사용해야 해결될 수 있는 문제라는 것이다.

아래의 몇 몇 오류들은 Bug가 있는 - 오류가 Patch되지 않은 - Oracle JDBC를 이용해 CachedRowSet을 사용할 경우 발생될 수 있는 Error Message들의 예로, 궁극적인 문제의 원인은 Date Type의 DB Field를 CachedRowSet의 'getDate()' Method로 참조할 때 발생하는 'java.lang.ClassCastException'이 바로 그 것이다.
 
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not roll back JDBC transaction; nested exception is java.sql.SQLException: Connection is closed.
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:583)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:501)
javax.servlet.http.HttpServlet.service(HttpServlet.java:627)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)

[ErrorMessage-1]
  • ErrorMessage-1
       Spring Framework를 연동했을 때 발생될 수 있는 예로 'ClassCastException' 발생으로 인해 해당 Transaction에 대한 Roll back을 Spring Framework이 AOP를 통해 수행하려는 상황에서 발생된 오류인데, 이 오류는 엄밀히 말하면 일반적인 상황에서는 발생하지 않을 수도 있다. 왜냐하면 이 상황은 필자의 경우로 'Javassist'를 이용해 Connection Object를 Intercept한 경우이기 때문이다.

org.springframework.jdbc.UncategorizedSQLException: SqlMapClient operation; uncategorized SQLException for SQL []; SQL state [null]; error code [0];
--- The error occurred in sqlMap.xml.
--- The error occurred while applying a result map.
--- Check the scope.select.
--- Check the result mapping for the 'createDate' property.
--- Cause: java.lang.ClassCastException: java.sql.Timestamp; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
--- The error occurred in sqlMap.xml.
--- The error occurred while applying a result map.
--- Check the scope.select.
--- Check the result mapping for the 'createDate' property.  
--- Cause: java.lang.ClassCastException: java.sql.Timestamp
[ErrorMessage-2]

  • ErrorMessage-2
       Spring Framework와(과) iBATIS를 연동했을 경우 debugging 과정에서 추출한 Error Message로 오류의 원인을 정확히 짚어내고 있다. 하지만 오류내용을 보면 마치 'java.sql.Timestamp' Type을 다른 Type으로 잘 못 Casting해 발생한 오류인 것 처럼 보인다.
사실과 다르게 말이다. Error Message에 보이는 'createDate'라는 Field는 분명 'Date' Type인 것이다. 또한 CachedRowSet이 아닌 ResultSet으로는 전혀 문제가 발생하지 않은 Code이다.

이 문제의 원인을 찾는데 상당히 많은 시간을 소요했으며 정신적 스트레스 또한 말 할 수 없이 많았다. 지금까지의 Posting 글이 그러했듯 같은 오류에 대해 적어도 필자 보다는 적은 시간과 노력으로 좋을 결과를 얻기를 바라는 마음이다.
국내 검색 Site에서는 관련 오류 내용을 검색조차 할 수 없었으나 역시 Googling을 통해 알게된 외국의 한 Community Site에 올린 누군가의 글로 해결의 실마리를 찾게 되었다. 물론 이 도 쉽지많은 않앗지만 말이다. 아무튼 도움이 되길 바라며 Posting한다.


출처 : http://mobicator.blogspot.kr/2010/04/oracle-jdbc-cachedrowset-implement-bug.html

Posted by 1010
02.Oracle/DataBase2014. 7. 23. 15:08
반응형

내가 지식이 얕은 건지, 어떻게든 돌아가게는 만든다는 주먹구구식 마인드 때문인지,
Oracle DB에 입력되어 있는 순차적인 데이터의 선택적 추출은
이 방법밖에 알지 못한다..

자, 이제 그 방법에 대해 알아보자.

먼저 MySQL 에서는 
# [MySQL]
SELECT a.love_id 
FROM love a
LIMIT 1000, 2000;


하면, 실제 입력된 데이터의 순서에 맞추어 1000번 부터 2000번까지의 데이터가 나온다.

하지만.... 망할 ORACLE 에는 없다는 거....

어떤 분들은 'rownum으로  WHERE rownum BETWEEN 1000 AND 2000 하면 되지 않느냐??'
라고 하는데..... 잘 찾아보면, rownum 은 결과 값에 매겨지는 상대적인 값이므로,
절대적인 신뢰감이 없고, 위의 수식도 먹지 않는다..... 
뭐.. 안 믿으시면 한 번 해보시라.. --;;

그럼.. 어떻게 해야할까?
우리의 친구 꼼수를 한 번 발동 시켜볼까?

* 해법 *
************************************************************************************************
# [Oracle]
1. 아래 쿼리는, 데이터가 실제적으로 들어간 순서에 맞게 rownum을 부여 하여,
그 값을 하나의 컬럼으로 출력될 수 있게 해준다.
SELECT a.rownum as rnum, a.love_id
FROM love a;

2. 1에서 만들어진 쿼리를 아래와 같이 서브쿼리화 하여, between 사용이 가능해진다.
SELECT b.love_id
FROM (SELECT a.rownum as rnum, a.love_id FROM love a) as b
WHERE b.rnum BETWEEN 1000 AND 2000;

************************************************************************************************

자, 그럼,, 저 위의 방식을 이용하여 마음껏(???) limit 인 척 해보자.


출처 : http://blackbull.tistory.com/25

Posted by 1010
02.Oracle/DataBase2014. 7. 23. 15:06
반응형

DBMS별 지원되는 최대 컬럼수(Oracle,ms-sql,mysql,db2)


지원되는 최대 컬럼수

사용된 DBMS
Oracle: Oracle8i Enterprise Edition Release 8.1.7.0.1 - Production
DB2   : DB2/NT 7.2.1
MySQL : 4.0.7-gamma-nt
MS-SQL: Microsoft SQL Server  2000 - 8.00.760 (Intel X86) 
            Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 3)

사용된 질의
create table long_test
(
id1     varchar(10),
id2     varchar(10),
id3     varchar(10),
id4     varchar(10),
id5     varchar(10),
id6     varchar(10),
id7     varchar(10),
id8     varchar(10),
id9     varchar(10),
id10    varchar(10),
id11    varchar(10),
id12    varchar(10),
.
.
.



오라클과 ms-sql의 경우, 최대 1000, 1024개의 컬럼을 지원한다고 명시되어 있다.
MySQL의 경우, 명시적인 언급을 찾을 수 없어서 실험을 통해 얻은 값이다.
DB2의 경우는 table space의 페이지크기와 컬럼의 데이터타입에 따라 가변적이다.

오라클 : 1000
MySQL  : 3230
MS-SQL : 1024
DB2    : 가변적. 최대 컬럼수를 구하는 공식은 다음과 같다.
         4K page size allows a maximum of 500 columns 
         8K, 16K, and 32K page sizes allow a maximum of 1012 columns. 
         The actual number of columns for a table is determined by the following formula: 
         Total Columns * 8 + Number of LOB Columns * 12 + Number of Datalink Columns * 28 <= row size limits for page size. 

This article comes from dbakorea.pe.kr (Leave this line as is) 

출처 : https://www.linux.co.kr/home2/board/subbs/board.php?bo_table=lecture&wr_id=233&sca=3&page=9


Posted by 1010
02.Oracle/DataBase2014. 4. 11. 16:40
반응형

I am connecting from Oracle to MS SQL Server through an ODBC connection using Oracle SQL Developer. (How)

I want to query the schema of the MS SQL database using the data dictionary mapping of the Oracle Database Gateway for ODBC.

This works well:

select * from all_tables@katimssql;

But this doesn't:

create table alltables_mssql as 
select * from all_tables@katimssql;

Output:

Error report:
SQL Error: ORA-00604: error occurred at recursive SQL level 1
ORA-02047: cannot join the distributed transaction in progress
ORA-06512: at "SYS.HS$_DDTF_SQLTABLES", line 58
ORA-06512: at line 1

Does anyone know the solution of this?
Thanks in advance,
Kati


 

Details:

Oracle Database 11g Express Edition Release 11.2.0.2.0
Microsoft SQL Server 2008 (SP2) - 10.0.4000.0 (X64)
ODBC Driver: SQL Server Native Client 11.0 (32 bit)

initkatimssql.ora:

HS_FDS_CONNECT_INFO=katimssql
HS_FDS_TRACE_LEVEL = ON
HS_TRANSACTION_MODEL = READ_ONLY_AUTOCOMMIT

I tried these, none of them worked, the error message is the same.

HS_FDS_TRACE_LEVEL = off
HS_TRANSACTION_MODEL = SINGLE_SITE
HS_TRANSACTION_MODEL = READ_ONLY_AUTOCOMMIT
HS_TRANSACTION_MODEL = READ_ONLY
Posted by 1010
02.Oracle/DataBase2014. 4. 11. 16:32
반응형

ora-02047: cannot join the distributed transaction in progress
Cause: Either a transaction is in progress against a remote database that does not fully support two phase commit, and an update is attempted on another database, or updates are pending and and an attempt is made to update a different database that does not fully support two phase commit.
Action: complete the current transaction and then resubmit the update request

출처 : http://dpldpl.tistory.com/7

ORA-02047 동작중의 분산 트랜잭션(transaction)는 결합할 수 없습니다.

 

원인은 이기종 데이터베이스 시스템 간의 인터페이스 작업 시(ex. oracle to mssql)

데이터 처리를 함에 있어서(ex. Insert, Update, Delete)

커밋을 하지 않았기 때문에 발생하는 오류이다.

 

단일 시스템에서는 커밋을 하지 않아도 무방하지만

이기종 시스템간의 데이터 처리시에는 DML 구문뒤에 꼭 커밋을 해주어야 한다.

 

by. 덕평물류 전산정보팀(DPL Dream Partner in Logitics)

Posted by 1010
02.Oracle/DataBase2013. 12. 30. 11:04
반응형

우리는 오라클 Sequence를

중요한 문서나 회원에 고유번호로 활용하는 경우가 종종 있다.

 

당연히 1부터 빈틈없이 자동으로 증가해줄것이라고 믿는다.

 

하지만 퍼포먼스를 위해 Sequence의 Cache를 사용했다면 그렇지 않을 수 있다.

 

Sequence의 Cache는...

원하는 숫자 만큼 미리 만들어 Shared Pool의 Library Cache에 상주 시키는 기능이다.

 

다시말해,

Cache가 저장된 수 만큼 Disk I/O가 발생하지 않는 장점도 있고

반면에 Library Cache가 날아간다면 Sequence Cache도 날아가게 된는 단점도 있다.

 

 

만약 Cache를 50으로 했다면 1~50까지 읽었가는(Nextval) 동안에는 Library Cache에서 읽어간다.

51번째를 요청할때 다시 50이 증가된 51~100까지 다시 Library Cache에 저장하는 것이다.

Disk I/O가 2번 밖에 발생하지 않았다.^^;;; 좋은 기능임엔 틀림없다.

 

하지만 오류 아닌 오류가 아래와 같이 있으므로 유의해서 사용해야 한다.

  1. DB가 비정상적으로 종료가 되었거나 (PMON이 죽거나, shutdown abort를 사용했을경우)

  2. Library Cache에서 우선순위 경합에 밀려 cache aged out 되는 경우다.
    다시말해, 사용 빈도가 적어 메모리에서 삭제되는 경우를 말한다.^^;

Cache 50에 Current값이 1이라고 할지라도....

위와 같은 현상이 발생한다면 다음 시퀀스는 51이 된다.

(참고: http://kr.forums.oracle.com/forums/thread.jspa?threadID=463133)

 

 

해결 방법은 2가지다.

  1. Cache를 사용하지 않는 것.
  2. dbms_shared_pool package를 이용해 Library Cache에 항상 상주하도록 강제설정하는 방법이다.

무엇보단 근본적인 방법인 1번을 추천하고 싶다. 특별한 경우가 아니면 Cache를 사용하지 말자....^^;;

 

출처 : http://blog.naver.com/PostView.nhn?blogId=xacti&logNo=80109572891&parentCategoryNo=3&viewDate=&currentPage=1&listtype=0

Posted by 1010
02.Oracle/DataBase2013. 2. 6. 11:41
반응형

출처 : http://dbclose.tistory.com/75

 

오라클은 MySql 에서 지원하는 order by LIMIT 를 지원하지 않아 페이징 처리 속도가 느리다.

알려진 오라클 페이징 기법을 테스트 하여 속도를 비교해 보자

SAMPLE 테이블에 100만건의 데이트를 입력 후 10개씩 보여지는 마지막 페이지를 조회
정렬 조건은 PRIMARY KEY DESC 정렬

1. 페이지 수를 계산하여 셀렉트

SELECT B.* FROM (
SELECT
CEIL(ROWNUM/10) PAGE
--, COUNT(*) OVER() AS TOTAL_COUNT
, A.* FROM (
SELECT * FROM OP_SAMPLE
ORDER BY ID DESC
) A
) B
WHERE PAGE = 100000;


100만 건인 경우 수행시간 : 1.327초, TOTAL_COUNT 까지 조회시 : 3.848초
10만 건인 경우 수행시간 : 0.135초 TOTAL_COUNT 까지 조회시 : 0.255초



2. ROW_NUMBER()를 이용한 페이징

SELECT * FROM (
SELECT A.*, ROW_NUMBER() OVER(ORDER BY ID DESC) AS NUM
FROM OP_SAMPLE A
)
WHERE NUM BETWEEN 999991 AND 1000000;

100만 건인 경우 수행시간 : 1.136초
10만 건인 경우 수행시간 : 0.253초



3. ROWNUM을 이용한 페이징

SELECT * FROM (
SELECT ROWNUM AS RNUM, Z.* FROM (
SELECT * FROM OP_SAMPLE ORDER BY ID DESC
) Z WHERE ROWNUM <= 1000000
) WHERE RNUM >= 999991;

100만 건인 경우 수행시간 : 0.826초
10만 건인 경우 수행시간 : 0.087초



4. ROWNUM + 인덱스를 이용한 페이징

SELECT * FROM (
SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM (
SELECT * FROM OP_SAMPLE
) Z WHERE ROWNUM <= 1000000
) WHERE RNUM >= 999991;

100만 건인 경우 수행시간 : 0.687초
10만 건인 경우 수행시간 : 0.079초



참고로 MySql 쿼리 조회시 (mysql 5, innoDB)

SELECT * FROM OP_SAMPLE
ORDER BY ID DESC LIMIT 999991, 10;

100만 건인 경우 수행시간 : 0.033초
Posted by 1010
02.Oracle/DataBase2013. 2. 5. 23:29
반응형

TOAD for Oracle Freeware 10.6 Free Download

Secure TOAD for Oracle Freeware Download Options

Download Now! TOAD for Oracle Freeware 10.6

More download options:

 

TOAD for Oracle Freeware 9.7

Thank you for downloading
Free Download

TOAD for Oracle Freeware 9.7


Simplify Oracle database administration tasks
  • Server 1 Brothersoft(US) Click to Download
  • Posted by 1010
    02.Oracle/DataBase2012. 10. 6. 01:03
    반응형

    Optimizer의 원리와 Tuning (상)

    Oracle Optimizer의 원리 이해 및 SQL & 애플리케이션의 튜닝(상):

    옵티마이저의 질의 처리 단계에 대한 이해

    오라클에서 사용하는 옵티마이저(Optimizer)는 크게 RBO(Rule Base Optimizer)와 CBO(Cost Base Optimizer) 2개로 구분된다. 1992년 Oracle 7에서 처음 CBO가 지원된 이래 새로운 기능들이 적용되면서 CBO가 계속 향상되고 있는 데 반해, RBO는 오라클이 더 이상은 추구하지 않는 옵티마이저로서, 현재의 Oracle Database 10g에서도 명맥은 남아 있지만 향후는 더 이상 지원되지 않을 것이다.

    옵티마이저의 입장에서 질의(query) 처리는 5단계로 나눌 수 있는데, 옵티마이저는 서브질의와 뷰의 병합(merge) 등을 수행하는‘Query Rewrite’단계와 ‘Query Optimization’단계에 참여한다. 여기서 옵티마이저는 ▲ 데이타를 어떠한 방법으로 액세스할 것이며 ▲ 올바른 결과를 어떻게 제공할 것이며 ▲ 데이타를 얼마나 효과적으로 액세스할 것인가를 결정한다. ‘QEP Generation’단계는‘Query Optimization’단계에서 제공된 정보를 이용해서 질의에 대한 최적의 실행계획(execution plan)을 만들어 내는 단계이다. CBO에서는 질의 실행계획(QEP)을 구하기 위하여 RBO보다 복잡한 단계를 거치게 된다<그림 1>, <표 1>.







    소프트 파싱과 하드 파싱

    SQL 문장이 옵티마이저에 의해 처리되고 그 결과물로서, SQL 문장이 어떻게 실행될 것인지의 정보, 즉, QEP가 생기게 된다. 이들 정보는 한번 쓰고 버리는 것이 아니라 오라클의 캐쉬(cache) 영역인 SGA의 공유 풀 (shared pool)에 이들 모든 정보를 캐쉬화해 관리한다. 다음 번에 같은 SQL 문장이 사용자에 의해서 실행되면, 이를 재활용하게 된다.

    SQL 문장이 실행되면, 우선 SQL 문장 텍스트의 스트링을 해쉬 함수를 통과시켜 결과 값에 해당되는 버킷(어레이형 구조)에 매달린 체인 정보에서 같은 SQL 문장이 존재하는지 찾는 처리절차를 수행하게 된다. 또한 같은 SQL 문장을 찾았어도 여러 버전이 존재할 수 있다. 여러 버전이란, 같은 SQL 문장(대/소문자, 화이트 스페이스 등이 모두 같아야 함)이지만 서로 다른 스키마의 테이블(예, scott의 emp, sys의 emp)이거나, 바인드 변수 를 사용한 경우는 바인드 변수의 타입, 길이 등에 의해서도 서로 다른 버전이 된다는 것이다. 이와 같이 같은 SQL 문장에 같은 버전을 찾았다면 이를 ‘소프트 파싱(soft parsing)’이라고 한다.

    그렇지만, 체인을 다 찾았는데 같은 문장을 발견하지 못했다면, 해당 SQL 문장이 Parsing/Optimizing 단계를 거친 결과로 나온 정보를 저장하기 위해 공유 풀로부터 메모리를 확보받고, 기록한 정보를 체인에 매달게 된다. 이를 ‘하드 파싱(hard parsing)’이라고 한다. 당연히 하드 파싱의 작업량이 소프트 파싱의 작업량에 비해 월등히 클 것이다<그림 2>.



    이와 같은 소프트 파싱과 하드 파싱의 과정을 생각해 볼 때, 집중적인 SQL 문장이 실행되는 OLTP(초당 수천 ~ 수만 개 이상)에서 하드 파싱이 많다면 어떻게 될까? 한정된 메모리인 캐쉬에 새로운 메모리를 계속 할당하고, LRU 알고리즘에 의해 제거하고, 체인에 매달고 끊는 등의 일들을 반복해야 할 것이다. 또한 하드 파싱은 복잡한 처리과정을 거치므로 많은 자원(CPU)을 사용하게 된다. 그러므로 OLTP 환경에서는 이와 같은 하드 파싱을 가능한 줄이도록 해야 한다. 특히 SQL 실행 규모가 큰 OLTP 업무는 1% 미만을 권장한다.

    애플리케이션을 개발할 때 이러한 하드 파싱을 줄이기 위한 방법으로 거의 대부분의 데이타베이스 접속 방식(JDBC, ODBC, ADO, PRO*C 등) 에서 자주 사용되는 SQL 문장들은 바인드 변수 기법들을 사용하여 개발하는 방법들을 제공하고 있다. 또한 일부에서는 소프트 파싱 자체도 줄일 수 있는 기법들을 제공하고 있다. 실제 이러한 기법을 적용해서 튜닝한 결과, 시스템 CPU/메모리 측면에서 40~50% 이상 개선된 사례가 많이 있다. 혹시 현재 운영중인 시스템이 사용자가 많아지면서 CPU 리소스가 급격히 증가해, 라이브러리 캐쉬, 공유 풀 경합 현상이 발생한다면, 이러한 점을 의심해 볼 수 있다.

    <표 4>는 SQL 문장을 바인드 변수를 사용한 공유 SQL과, 상수를 결합한 형태로 SQL 문장을 만들어 실행시키는 비공유 SQL을 9,999회 실행시켜 오라클의 공유 풀 메모리 사용현황과 파싱시 CPU 사용시간을 테스트 한 것이다(단, 그 결과치는 실행 서버별로 차이가 있다).



    결론적으로 보면, 비공유 SQL 방식의 사용 메모리와 CPU 사용률이 실행 규모에 비례해 증가하고, 실행된 SQL 문장이 기존에 캐쉬화되어 있는 SQL 문장들을 밀어내는 역할을 한다는 것을 알 수 있다.

    이와 같은 SQL 문장을 공유하기 위해서 오라클 입장에서 처리해주는 CURSOR_SHARING이라는 파라미터를 제공하기도 한다. 그러나, CURSOR_SHARING은 모든 상수를 다 바인드 변수로 바꿔버리기 때문에 개발자가 의도하지 않은 Literal까지도 바꾸게 되므로, 애플리케이션을 수정할 수 있다면 가능한 애플리케이션 단에서 바인드 변수를 사용하는 것이 효과적이다.

    하드 파싱을 줄이기 위해 모든 업무에 바인드 변수 사용방법을 적용하는 것은 잘못된 생각이다. 옵티마이저의 입장에서 보면, 바인드 변수 기법 보다는 Literal을 사용한 비공유 SQL 방식을 좋아한다. Literal SQL 문장일 경우는 상수 값에 따라서 범위를 정확히 알 수 있기 때문에 효과적인 플랜을 결정하는 주요 결정요소로 작용하기 때문이다. 즉 바인드 변수 기법은 옵티마이저의 판단에는 좋지 않지만 SQL 문장이 집중적으로 실행되는 OLTP 환경에서 하드 파싱의 비율을 줄이기 위한 방법인 것이다. 즉, 업무의 특징에 따라서 다른 적용방식이 사용되어야 한다. 다음은 OLTP와 DW의 특징에 따라 다르게 고려되어야 할 사항이다.

    • OLTP의특징
    - 목표 : 신속한 응답시간, 적은 데이타 처리량
    - 파싱 타임을 최소화하고 SQL 등이 공유될 수 있도록 바인드 변수를 사용해야한다.
    - 인덱스의 사용률이 높아야 한다.
    - 정렬(sorting)을 최소화해야 한다.
    - Nested Loop Join(FIRST_ROWS_n) 방식으로 많이 유도한다.

    • DW의특징
    - 목표 : 최고의 처리량, 방대한 데이타 처리량
    - 인덱스의 참조는 중요한 사항이 아니다.
    - 정렬 또는 Aggregate함수 등이 중요한 역할을 한다.
    - Hash Join 등을 많이 사용하도록 유도한다.
    - 파싱 타임 등은 그리 중요하지 않으며, 바인드 변수의 사용이 문제가 될 수 있다.

    - 병렬 질의 등의 사용률을 높인다.

    Rule Base Optimizer

    의 최적화(query optimization)에서 RBO(Rule Base Optimizer)는 정해진 랭킹(ranking)에 의해 플랜을 결정한다. 같은 랭킹이라면 Where 절의 뒤부터, From절 뒤의 객체가 우선 순위를 갖는다. 한 객체(예 : 테이 블)에서 같은 랭킹의 인덱스가 있다면 가장 최근에 만들어진 인덱스를 사 용한다. 이는 CBO(Cost Base Optimizer)에서도 같이 적용되는 사항이다.

    다분히 RBO는 개발자들이 프로그래밍 단계에서 SQL 문장 구조의 인위적인 조정 등으로 인덱스를 사용 못하게 하는 등 개발자가 코딩에 신경을 많이 써야 하는 문제점이 있다. 또한 RBO는 해당 질의에 대한 테이블의 인덱스가 존재한다면 전체 90% 이상의 대상이어도 인덱스를 선택한다는 것이다. 즉, RBO는 무조건 다음과 같은 미리 정해진 룰을 기준으로 플랜을 결정하게 된다. 1992년 Oracle 7에서 CBO가 지원되면서 CBO는 계속적인 신기능의 적용으로 발전해 온 반면, RBO는 더 이상의 기능 향상은 없으며, 향후는 CBO만 지원될 계획이다. 그러므로 RBO에 더 이상의 미련을 갖지 말기 바라며, CBO의 훌륭한 기능들을 적극 활용하길 바란다.

    다음은 RBO의 랭킹을 정리한 것이다.

    Path 1 : Single Row by Rowid
    Path 2 : Single Row by Cluster Join
    Path 3 : Single Row by Hash Cluster Key with Unique or Primary Key
    Path 4 : Single Row by Unique or Primary Key
    Path 5 : Clustered Join
    Path 6 : Hash Cluster Key
    Path 7 : Indexed Cluster Key
    Path 8 : Composite Index
    Path 9 : Single-Column Indexes
    Path 10 : Bounded Range Search on Indexed Columns
    Path 11 : Unbounded Range Search on Indexed Columns
    Path 12 : Sort-Merge Join
    Path 13 : MAX or MIN of Indexed Column
    Path 14 : ORDER BY on Indexed Column
    Path 15 : Full Table Scan

    특히 Path 8, 9, 10에 주의를 해야 한다. 예를 들면, ‘emp’ 테이블에 ‘A’ 인덱스가 “deptno”로 구성되어 있고, ‘B’ 인덱스가 “deptno + empno”로 구성되어 있다면, 다음과 같은 SQL 문장은 ‘A’ 인덱스를 사용 하게 된다. 조건이 Bounded Range Search(Between)로 왔기 때문에 아래의 SQL 문장에서 (A)와 (B)의 랭킹은 (A) ==> Rank 9 , (B) ==> Rank 10 조건이 되므로 싱글 칼럼 인덱스를 사용한다는 것이다.



    그러면, 이제CBO에 대해 살펴보기 전에, 참고로 RBO를 CBO로 전환한 사례를 잠깐 소개하겠다.

    현재 RBO를 사용하고 있는 상황에서 마이그래이션시 CBO로 전환하고 싶으나, 막연히 두려운 부분도 많을 것이다. 실제 RBO에서 CBO로 전환하고 나서 가장 효과를 보는 부분은 배치잡 형태이다. 특히 Oracle9i Database 이상의 WORKAREA_SIZE_POLICY=AUTO로 운영하는 곳 이라면 더욱 더 그럴 것이다. 그러나 OLTP의 변화는 조심해야 한다. 아래의 경우는, 이전하면서 옵티마이저 모드를 RBO에서 CBO로 전환한 것 뿐만 아니라, 블록 사이즈와 CBO 옵티마이저에 민감한 db_file_multi block_read_count 값도 크게 늘렸다. 특히 WORKAREA_SIZE_ POLICY=AUTO로 필요한 워킹 메모리(Sort, Hash, Bitmap 등)를 옵티마이저가 판단하에 가능한 충분히 사용하게 하는 방식을 사용하였다.

    그러다 보니, CBO에 영향을 주는 소트 메모리와 해쉬 메모리가 풍부하게 되었고, 블록 사이즈도 커졌으며, 풀 테이블 스캔의 정도를 결정하는 db_file_multiblock_read_count 값도 아주 커진 상태이다. 또한 마이그래이션되면서 데이타가 재정리되어 있는 상태이므로, 풀 테이블 스캔과 Sort Merge Join, Hash Join의 경향이 커진 상태이다. 그러므로 배치잡의 경우는 최적의 조건이 되었으나, 기존에 주로 Nested Loop Join을 선호하 던 RBO 환경의 OLTP들은 많은 플랜의 변화에 직면하게 된다. 그러면 이러한 부분을 어떻게 보정해 줄 것인가?

    optimizer_index_caching, optimizer_index_cost_adj의 파라미터가 그 해답일 것이다. 가능한 Nested Loop Join를 선호하고, CBO의 옵티마이저 모드가 인덱스에 점수를 더 주어서 인덱스의 비중을 키울 수가 있는 것이다. 물론 이러한 전환형태 말고 옵티마이저 모드를 FIRST_ROWS_n으로 운영하거나, 아웃라인을 이용하는 방법 등도 있을 것이다. 여러 방법이 있겠지만, 필자는 아래와 같은 방법을 선호한다.

    다음은 마이그래이션시 RBO에서 CBO로 전환한 사례이다.



    Cost Base Optimizer

    질의 최적화에서 CBO(Cost Base Optimizer)는 해당 SQL 문장이 참조하고 있는 객체들(테이블, 인덱스 등)에 대한 수집된 통계정보(statistics)의 값과 데이타베이스 파라미터(init.ora) 설정 값을 기초로 가장 적은 비용 (cost)이 발생되는 플랜을 결정하는 옵티마이저 방식이다.

    여기서 중요한 사실은 RBO에서는 전혀 사용되지 않았던 통계정보를 CBO에서는 이용한다는 것이다. 이들 통계정보는 DBA에 의해서 또는 자동 수집 기능(Oracle9i Database Release 2, Oracle Database10g)에 의해 객체들의 통계정보를 관리하는 시스템 딕셔널리(Dictionary)에 저장되고, 이 정보를 CBO 옵티마이저가 이용하는 것이다. 이들 정보는 SQL 문장을 실행하는 데 얼마만큼의 I/O 횟수가 발생할 것인가를 계산하기 위한 각종 데이타를 가지고 있다. 여기서 중요한 사실은 I/O 크기는 중요하지 않으며 I/O 횟수가 중요하다는 것이다. 즉, CBO 옵티마이저는 SQL 문장에 대한 여러 가지 경우의 수별로 I/O의 횟수에 비례한 비용을 산출해내고, 이들 비용에서 가장 작은 비용을 갖는 플랜을 결정한다는 것이다. 즉, 비용 은 I/O 횟수에 비례하는 값이라고 보면 쉬울 것이다.

    그러나, Oracle Database 10g부터는 비용의 단위 기준이 I/O에서 처리시간으로 바뀌었다(time base). 또한 Oracle9i Database부터 시스템 통계정보(CPU, 디스크 액세스 타임)를 이용해서 I/O로 환산한 방식을 제공하였으나, 이것은 단지 옵션이었다. 그러나, Oracle Database 10g부터는 시스템 통계정보(CPU, 디스크 액세스 타임)를 이용해서 처리시간으로 환산한 방식을 디폴트로 사용하므로 상당히 정확한 플랜을 만들어내며, 실행 예측 시간도 상당히 정확하다.

    그러면, 여기서 잠깐 CBO에서 사용되는 통계정보가 저장된 Dictinary 정보 예를 참고로 살펴보자.

    [USER|ALL|DBA]_TABLES : Table의 통계정보
    NUM_ROWS,BLOCKS,AVG_ROW_LEN,SAMPLE_SIZE,LAST_ANALYZED

    [USER|ALL|DBA]_INDEXES : Index의 통계정보
    BLEVEL,LEAF_BLOCKS,DISTINCT_KEYS,AVG_LEAF_BLOCKS_PER_KEY,
    AVG_DATA_BLOCKS_PER_KEY,CLUSTERING_FACTOR,NUM_ROWS,SAMPLE_SIZE,
    LAST_ANALYZED

    [USER|ALL|DBA]_TAB_COLUMNS : Column의 통계정보
    NUM_DISTINCT,LOW_VALUE,HIGH_VALUE,DENSITY,NUM_NULLS,NUM_BUCKETS,
    LAST_ANALYZED,SAMPLE_SIZE,AVG_COL_LEN

    [USER|ALL|DBA]_TAB_HISTOGRAMS : Column의Data 분포도 정보
    TABLE_NAME,COLUMN_NAME,ENDPOINT_NUMBER,ENDPOINT_VALUE,
    ENDPOINT_ACTUAL_VALUE

    기타 파티션 / 클러스터 등에 대한 통계정보

    그러면“select * from dept where deptno = 10”과 같은 SQL 문장을 실행해야 한다고 가정하자. 여기서 dept Table은 deptno에 대한 인덱스가 있고, 테이블은 전체 10 블록으로 구성되어 있으며, 풀 테이블 스캔일 경우 I/O 단위를 결정하는 파라미터는 DBA가 db_file_multiblock_read_count=8로 지정하여 운영하고 있다고 가정하자 . 여기서 RBO라면 무조건 인덱스를 타는 플랜을 결정하였을 것이다.

    그러나, CBO의 경우는 deptno의 인덱스를 이용해 실행하면, 3회(인덱스는 싱글 블록 단위 I/O)의 I/O가 발생한다고 가정하고, 풀 테이블 스캔의 경우는 2회(8블록 + 2블록)의 I/O가 발생한다고 가정하면, CBO에서는 인덱스가 있음에도 I/O 횟수 측면에서 더 효과적인 풀 테이블 스캔을 선택한다는 것이다. 그러므로 CBO는 이와 같이 가능한 정확한(현실 데이타와 맞는) 통계정보와 적절한 데이타베이스의 파라미터인 init.ora에 의해 플랜이 결정되는 것이다.

    CBO에서만 가능한 기능들

    CBO는 오라클의 신기능을 지원하도록 지속적으로 발전하고 있으며, 다음의 경우는 반드시 CBO에서만 플랜 결정시 검토되거나 무조건 CBO로 동작되는 경우이다. 예를 들어, 파티션 테이블을 사용한다면 통계정보가 없더라도 무조건 CBO로 동작된다는 것이다.

    • Partitioned tables (*)
    • Index-organized tables
    • Reverse key indexes
    • Function-based indexes
    • SAMPLE clauses in a SELECT statement (*)
    • Parallel execution and parallel DML
    • Star transformations
    • Star joins
    • Extensible optimizer
    • Query rewrite (materialized views)
    • Progress meter
    • Hash joins
    • Bitmap indexes
    • Partition views (release 7.3)
    • Hint (*)
    • Parallel DEGREE & INSTANCES - ‘DEFAULT’도 해당 (*)

    CBO의 옵티마이저에 영향을 줄 수 있는 파라미터

    옵티마이저가 플랜을 수립하는 데 영향을 줄 수 있는 파라미터 값이 무엇인지를 알고 있는 것이 무엇보다 중요하다. 실제 옵티마이저가 참조하는 파라미터는 Oracle9i Database 기준으로 보더라도 60여 개에 이른다. 특히 DBA는 이들 옵티마이저의 파라미터 설정에 신중해야 한다. 또한 이들 파라미터의 효과적인 설정은 개발 중이거나, 마이그래이션 중에 업무의 특징을 판단한 다음, 해당 업무에 가장 효과적인 것을 설정해야 한다. 기준이 잘못되면 개발자들은 SQL 문장마다 힌트를 넣기 바쁠 것이고, 많은 인적 자원을 튜닝에 소모해야 할 것이다. 그러므로 대부분의 업무들이 최적화되어 잘 운영될 수 있는 형태로 이들 파라미터를 바꿔가면서 기준을 정하는 것이 중요하다. 물론 이들 값보다도 CBO에서 사용되는 통계정보가 중요하다는 것은 당연한 사실이다. 오라클에서는 개발장비에도 운영장비에 있는 통계정보와 같게 운영할 수 있도록 DBMS_STATS 패키지를 제공한다.

    다음은 질의 수행시 옵티마이저가 플랜을 수립하기 위해 참조한 파라미터 중 일부이다(버전마다 다르다).

    OPTIMIZER_PERCENT_PARALLEL (Default = 0)
    Optimizer_Percent_Parallel의 Parameter는 CBO가 비용을 계산하는 데 영향을 주는 파라미터이다. 즉 수치가 높을수록 병렬성을 이용하여 풀 테이블 스캔으로 테이블을 액세스하려고 한다. 이 값이 0인 경우는 최적의 시리얼 플랜이나 패러렐 플랜을 사용하며, 1~100일 경우는 비용 계산에서 객체의 등급을 사용한다.

    OPTIMIZER_MODE (Default=Choose(Oracle7 ~ Oracle9i
    Database),ALL_ROWS)
    {Choose(<=9i)|Rule(<=9i)|First_rows|First_rows_n(> =Oracle9i)|All_rows}

    기본적인 옵티마이저 모드를 결정한다 (왼쪽상자 기사 ‘옵티마이저 모드의 종류 및 특징’ 참조).

    HASH_AREA_SIZE, HASH_JOIN_ENABLED (Oracle Database 10g : _ hash_join_enabled=true)
    위의 파라미터 값에 따라서 Hash Join으로 유도할 수 있다. Hash Join이 가능하고 해쉬 메모리가 충분하다면, 플랜에 Hash Join의 경향이 커진다.

    OPTIMIZER_SEARCH_LIMIT (Default = 5)
    옵티마이저에게 조인 비용을 계산할 경우, From절에 나오는 테이블의 개수에 따라서 조인의 경우의 수가 있을 수 있으며, 옵티마이저는 이들 각각의 경우의 수에 대한 조인 비용을 계산하게 된다. 물론 일부 예외사항은 있다. 예를 들어, Cartesian Production Join 등은 우선 순위가 낮으므로 뒤로 미뤄질 것이다. 이 파라미터의 값이 5일 경우 From절에 5개의 테이블에 대해서 모든 조인의 경우의 수를 가지고 비용을 계산하게 되며, 그 개수는 5!=120개의 경우의 수 에 대한 조인 비용을 계산하게 되므로 옵티마이저가 많은 시간을 소모하게 되므로 성능에 영향을 미칠 수도 있다.

    SORT_AREA_SIZE , SORT_MULTIBLOCK_READ_COUNT
    위의 파라미터의 값에 따라서 Sort Merge Join으로 유도할 수 있다. 소트 메모리가 충분하다면, 플랜에 Sort Merge Join의 경향이 커진다.

    DB_FILE_MULTIBLOCK_READ_COUNT
    이 파라미터의 수치가 클수록 인덱스 스캔보다는 풀 테이블 스캔의 비중이 높아진다. 이 파라미터는 옵티마이저의 플랜 결정에 민감하게 영향을 주는 값이다. 즉, 이 값이 커지면 풀 테이블 스캔과 병행해서 Sort Merge Join 또는 Hash Join의 경향이 커진다.

    OPTIMIZER_INDEX_CACHING (Default = 0)
    CBO가 Nested Loop Join을 선호하도록 조절하는 파라미터, Nested Loop Join시 버퍼 캐쉬 내에 이너 테이블의 인덱스를 캐쉬화하는 비율(%)을 지정하므로 Nested Loop Join시 성능이 향상되며, 옵티마이저는 비용 계산시 이 비율을 반영하여 Nested Loop Join을 선호하도록 플랜이 선택된다(0~100). 100에 근접할수록 인덱스 액세스 경로가 결정될 가능성이 높다. 기존의 RBO를 CBO로 전환시 옵티마이저를 RBO 성향으로 보정하는 데 효과적이다.

    OPTIMIZER_INDEX_COST_ADJ (Default = 100)
    옵티마이저가 인덱스를 사용하는 위주의 플랜으로 풀릴 것인지 또는 가능한 사용하지 않을 쪽으로 풀릴 것인지의 비중을 지정한다. CBO는 RBO처럼 인덱스를 사용하도록 플랜이 주로 만들어지게 되나, 인덱스가 있다고 해서 RBO처럼 인덱스를 이용한 플랜으로 처리되는 것은 아니다. 인덱스를 이용하는 플랜 위주로 하고자 한다면 100(%) 이하를, 가능한 인덱스를 사용하지 않고자 한다면 100 이상을 지정한다(1 ~ 10000). 이 파라미터는 기존의 RBO를 CBO로 전 환시 옵티마이저를 RBO의 인덱스 위주 성향으로 보정하는데 효과적이다.

    WORKAREA_SIZE_POLICY (AUTO | MANUAL)

    옵티마이저가 [HASH|SORT|BITMAP_MERGE|CREATE_ BITMAP] *_AREA_SIZE를 자동으로 결정하는 PGA 자동 관리 방식으로, 인스턴스에 속한 모든PGA의 메모리의 합이PGA_AGGREGATE_TARGET에서 설정된 메모리를 가능한 넘지 않는 범위 내에서 Workarea(Sort, Hash, Bitmap 등)를 충분히 사용하고자 하는 방식이다. 플랜은 할당된 Workarea를 가지고 플랜을 결정하게 되므로 풍부한 메모리에 의해 Hash Join, Sort Merge Join등을 선호하는 경향이 높다. 내부적으로 히든 파라미터로 *_AREA_SIZE의 값을 가지고 플랜을 결정할 수도 있으나 인위적인 설정 없이는 자동 할당된 메모리로 플랜이 결정된다.

    OPTIMIZER_DYNAMIC_SAMPLING (Default = 1(Oracle9i Database), 2(Oracle Database 10g))

    더 나은 플랜을 결정하기 위한 목적으로 더 정확한 Selectivity & Cardinality 를 구하기 위한 방법으로 0 ~ 10 레벨이 있으며, 레벨이 높을수록 SQL 문장의 실행 시점에 통계정보를 만들기 위해 테이블의 데이타를 샘플링하기 위한 추가적인 Recursive SQL이 발생된다.

    DYNAMIC_SAMPLING(0 ~ 10) 힌트를 통해서도 같은 기능을 할 수 있다. 그러나 내부적으로 추가적인 테이블 액세스 의 비용이 발생하므로OLTP에서는 주로 사용하지 않는다. 특히OLTP 환경에 서 레벨을 디폴트 값 이상 높여 놓지 않도록 한다. Oracle Database 10g의 경우 통계정보가 없다면 ‘다이나믹 샘플링’이 적용된다.


    다음은 Oracle Database 10g의 플랜 및 다이나믹 샘플링의 예이다.

    옵티마이저 모드의 종류 및 특징

    • 인스턴스 레벨 : optimizer_mode = {Choose|Rule|First_rows| First_rows_n| All_rows}

    • 세션 레벨 : 인스턴스 레벨에 우선
    ALTER SESSION SET optimizer_mode =
    {Choose|Rule|First_rows|First_rows_n|All_rows}

    • 스테이트먼트 레벨 : 힌트를 사용하며, 인스턴스, 세션 레벨에 우선

    • Oracle9i Database에서 FIRST_ROWS_n 옵티마이저 모드가 추가되었음(N : 1, 10, 100, 1000).

    • Oracle Database 10g에서는 CHOOSE, RULE 모드는 더 이상 지원되지 않으나, 기능은 남아있다.

    • OPTIMIZER_MODE=CHOOSE 일경우
    통계정보가 없다면 기본적으로 RBO로 플랜이 결정된다. 그러나, ‘RULE’, ‘DRIVING_SITE’ 힌트 이외의 힌트가 왔다면 CBO로 결정된다(힌트는 룰의 규정을 깨므로 CBO로 동작됨).

    • Parallel Degree, Partition Table, SAMPLE절 등이 있으면 무조건 CBO

    • OPTIMIZER_MODE=First_rows|First_rows_n|All_rows일 경우 통계 정보의 존재 여부와 관계 없이 무조건 CBO로 처리하려고 함.

    통계정보가 없다면 Heuristics Value를 이용하거나, Oracle9i Database 이상일 경우는 다이나믹 샘플링의 레벨에 따라 테이블의 데이타를 샘플링해서 CBO로 플랜이 결정된다. 그러나, 플랜이 비효율적일 수 있다.

    • 통계정보가 있으나 옵티마이저 모드가 RULE일 경우, 다른 힌트가 오지 않은 경우와 Parallel Degree, Partition Table, SAMPLE절 등이 나오지 않은 경우는 RBO로 처리된다.

    CBO를 위한 통계정보 운영 방법

    통계정보는 CBO의 플랜 결정에 사용되는 객체들의 물리적인 구성정보를 나타낸다. 즉, 테이블이 몇 블록으로 구성되어 있으며, 몇 건의 로우들을 가지고 있으며, 평균 로우 길이는 어느 정도이며, 칼럼의 Min/Max 값의 분포, Distinct 값, 인덱스의 레벨, 키(key)당 Leaf Block 수 등의 정보들을 나타낸다. 이들 정보는 CBO의 플랜 결정의 기초 자료로 사용된다. 이들 통계정보를 생성하기 위해서는 ANALYZE 명령어를 이용하거나 DBMS_STATS 패키지를 이용하면 된다. 그러나 2개의 차이점에 주의해야 하며, DBMS_STATS를 지원하는 Oracle8i Database 이상부터는 DBMS_STATS를 사용하기를 권장하고 있다.

    Analyze 명령어와 DBMS_STATS의 차이점

    Analyze는 Serial Statistics Gathering 기능만 있는 반면, DBMS_STATS은 Parallel Gathering 기능이 있다.

    Analyze는 파티션의 통계정보를 각 파티션 테이블과 인덱스에 대해서 수집하고, Global Statistics는 파티션 정보를 가지고 계산하므로, 비정확할 수 있다. 그러므로 파티션 또는 서브파티션이 있는 객체에는 DBMS_STATS를 사용하여야 한다.

    DBMS_STATS은 전체 클러스터에 대해서는 통계정보를 수집하지 않는다. 그러므로 Analyze를 사용한다.

    DBMS_STATS은 CBO와 관련된 통계정보만을 수집한다. 즉, 테이블의 EMPTY_BLOCKS, AVG_SPACE,CHAIN_CNT 등은 수집되지 않는다.

    DBMS_STATS은 사용자가 지정한 통계정보 테이블에 수집된 통계정보를 저장할 수 있고, 딕셔너리로 각 칼럼, 테이블, 인덱스, 스키마 등을 반영할 수 있다.

    DBMS_STATS IMPORT/EXPORT 기능 및 추가적인 기능이 많다. 이 기능을 이용하여 운영 DB의 통계정보를 개발장비의 통계정보로 복사할 수 있으므로 개발장비의 플랜을 운영장비와 같게 만들 수 있다(매뉴얼 참조).

    다음은 Analyze 명령어에만 있는 기능이다.

    • Structural Integrity Check 기능
    analyze { index/table/cluster } (schema.){ index/table/cluster } validate structure (cascade) (into schema.table);

    • Chained Rows 수집 기능
    ANALYZE TABLE order_hist LIST CHAINED ROWS INTO ;

    시스템 통계정보 (>= Oracle9i Database)

    시스템 통계정보는 객체의 통계정보와 같이 사용되는 정보로서, 기존의 Oracle8i Database까지의 I/O 중심의 플랜 방식에 CPU와 디스크 I/O 속도와 같은 시스템 자원의 효율을 반영하여 보다 효율적인 플랜을 결정하기 위한 방법으로, Oracle9i Database에서 처음 소개되었으며, 옵션 기능으로 DBA에 의해 사용될 수도 있고 사용하지 않을 수도 있었다. Oracle Database 10g에서는 시스템 통계정보가 기본적으로 수집되고 사용된다. 그러므로 기존의 I/O 횟수만 가지고 플랜을 결정하던 부분이 Oracle Database 10g에서는 시스템의 자원의 성능도 고려되어 보다 정확한 플랜을 결정할 수 있게 되었다.

    이들 시스템 통계정보는 DBMS_STATS 패키지를 이용해서 수집된다.

    Oracle Database 10g에서는 기본적으로 수집되는 값들이 있으며, 또한 사용자가 수집해야 하는 항목도 있다. 다음은 Oracle Database 10g의 시스템 통계정보의 수집형태를 보여주고 있다.



    더 자세한 내용 : 오라클 매뉴얼 [Database Performance Tuning Guide and Reference]

    http://munduki.tomeii.com/darkhorse/entry/ORACLE-Oracle-Optimizer의-원리-이해-및-SQL-애플리케이션의-튜닝 상

    Posted by 1010
    02.Oracle/DataBase2012. 9. 21. 06:30
    반응형


     

    1. Ranking Family

    (1) RANK() - 상위 순으로 등수를 부여하는 경우 정렬 결과를 기준으로 전체 순위를 출력

    ☞사용법

    RANK() OVER(

    [PRTITION BY < value expression1>] [,...]

    ODER BY<value expression2> [collate clause] [ASC:DESC]

    [NULLS FIRST:NULLS LAST])

    OVER : 순위를 부여하기 위한 대상 집합의 정렬 기준과 분할 기준 정의

    PARTITION BY : value expression1을 기준으로 분할, 생랼하면 전체 집합을 대상으로 순위부여

    ODER BY : 각 분할내에서 데이터를 정렬하는 기준 칼럼 지정

    NULLS FIRST|NULLS LAST : 정렬 결과에서 NULL값의 위치 지정


    (2) DENSE_RANK() - RNAK함수의 변형 동일 순위를 무시한 연속 순위를 출력
    RNAK함수는 1등이 2건인 경우 다음순위를 3등으로 부여 하지만, DENSE_RANK 함수는 다음순위를 2등으로 부여한다.

    질의

    SELECT id,score,
    rank()over(ORDER BY score ASC)as rank,
    dense_rank() over(order by score asc)as dense_rank
    From ksdb_score;

    결과
    ID SCORE RANK DENSE_RANK
    --------- ---------- ---------- ----------
    200040394 83 1 1
    200020182 88 2 2
    200231047 89 3 3
    200020182 90 4 4
    200020183 90 4 4
    200020183 92 6 5
    200172058 93 7 6
    200040394 95 8 7


    (3) CUME_DIST()- 그룹 값 내에서 어떤 값의 cumulative distribution(누적분포)을 계산

    ☞사용법

    CUME_DIST(expr)


    (4) PERCENT_RANK()-


    (5) NTILE() - 출력결과를 사용자가 지정한 그룹 수로 나누어 출력

    ☞사용법

    NITLE(expr) OVER(

    [PARTITION BY< value expression1>][,...]

    ORDER BY <value espression2> [collate clause] [ASC : DESC]

    [NULLS FIRST:NULLS LAST])


    질의

    SELECT Cid,bday,
    NTILE(3) OVER(ORDER BY bday) class
    FROM ksdb_customer_info;

    결과

    CID BDAY CLASS
    ------ -------- ----------
    100004 60/05/02 1
    100010 72/08/02 1
    100011 74/09/21 1
    100006 75/04/05 1
    100001 75/07/01 2
    100002 77/02/01 2
    100007 80/01/04 2
    100003 80/01/25 2

    100009 81/01/30 3
    100005 82/06/01 3
    100008 85/04/04 3

    11 개의 행이 선택되었습니다.

    (6) ROW_NUMBER() - 분할별로 정렬된 결과에 대해 순위를 부여하는 기능 분할은 전체 행을 특정 칼럼을 기준으로 분리하는 기능으로 GROUP BY 절에서 그룹화하는 방법과 같은 개념

    ☞사용법

    ROW_NUMBER() OVER(

    [PARTITION BY< value expression1>][,...]

    ORDER BY <value espression2> [collate clause] [ASC : DESC]

    [NULLS FIRST:NULLS LAST])

    질의

    SELECT id,score,
    RANK()OVER(ORDER BY score ASC)as rank,
    DENSE_RANK()OVER(order by score asc)as dense_rank,
    ROW_NUMBER()OVER(order by score asc)as row_number
    From ksdb_score;

    결과
    ID SCORE RANK DENSE_RANK ROW_NUMBER
    --------- ---------- ---------- ---------- ----------
    200040394 83 1 1 1
    200020182 88 2 2 2
    200231047 89 3 3 3
    200020182 90 4 4 4
    200020183 90 4 4 5
    200020183 92 6 5 6
    200172058 93 7 6 7
    200040394 95 8 7 8

    8 개의 행이 선택되었습니다.

    ↑RANK나 DENSE_RANK에서는 점수가 같으면 순위가 같게4,4 나오지만 ROW_NUMBER에서는 순서대로 4,5 로 번호를 부여한다.

    2. Aggregate Family

    (1) SUM(), AVG(), MAX(), MIN()

    ☞사용법

    AVG([DISTINCT|ALL] expr)

    SUM([DISTINCT|ALL] expr)


    expr의 데이터 타입은 NUMBER 데이터 타입만 가능

    (2) COUNT() - 테이블에서 조건을 만족하는 행의 개수를 반환

    COUNT(*)는 NULL을 가진 행과 중복되는 행을 모두 포함하는 행의 수를 계산

    COUNT(expression)는 NULL을 가진 행을 제외한 행의 수를 계산하여 반환

    (3) STDDEV() VARIANCE() - 인수로 지정된 칼럼에 대해 조건을 만족하는 행을 대상으로 표준편차와 분산을 구하는 함수로 숫자 데이터 타입에만 사용할 수 있으며, NULL 은 계산에서 제외된다.

    (4)RATIO_TO_REPORT()

    3. Lead/Lag Family

    LEAD() LAG() - 동일한 테이블에 있는 다른 행의 값을 참조하기 위한 함수

    LAG 분석함수는 현재 행을 기준으로 이전값을 참조

    LEAD 분석함수는 현재 행을 기준으로 이후값을 참조

    LEAD LAG 분석함수에서 지정하는 인수는 현재행을 기준으로 몇 번째 행을 참조할 것인지를 지정

    음수는 사용할 수 없다.

    ☞사용법

    ROW_NUMBER() OVER(

    [PARTITION BY< value expression1>][,...]

    ORDER BY <value espression2> [collate clause] [ASC : DESC]

    [NULLS FIRST:NULLS LAST])


    질의

    SELECT id,score,
    LEAD(score, 1)OVER(ORDER BY score)as next_score,
    LAG(score,1)OVER(order by score)as prev_score
    From ksdb_score;

    결과

    ID SCORE NEXT_SCORE PREV_SCORE
    --------- ---------- ---------- ----------
    200040394 83 88
    200020182 88 89 83 ←88 이후 점수 : 89
    200231047 89 90 88 88 이전 점수 : 83
    200020182 90 90 89
    200020183 90 92 90
    200020183 92 93 90
    200172058 93 95 92
    200040394 95 93

    8 개의 행이 선택되었습니다.

    [출처] 분석함수|작성자 은영

    Posted by 1010
    02.Oracle/DataBase2012. 9. 21. 05:34
    반응형

    Oracle PIVOT

    A common requirement for queries is to turn rows into columns, or the other way around.

    In Excel, we can do this using TRANSPOSE, a bit of patience & know-how, and ctrl+shift+enter.


    In Oracle, if we have aggregate data displaying months by row, the old way was to use a bunch of DECODEs (or similar)
    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
    SELECT t.name
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'jan',1,0)) jan
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'feb',1,0)) feb
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'mar',1,0)) mar
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'apr',1,0)) apr
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'may',1,0)) may
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'jun',1,0)) jun
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'jul',1,0)) jul
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'aug',1,0)) aug
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'sep',1,0)) sep
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'oct',1,0)) oct
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'nov',1,0)) nov
    ,SUM (DECODE (TO_CHAR (e.start_date,'mon'),'dec',1,0)) dec
    FROM events e, bookings b, resources r, resource_types t
    WHERE e.event_no = b.event_no
    AND r.code = b.resource_code
    AND r.type_code = t.code
    GROUP BY t.name;
    NAME JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
    -------------------- --- --- --- --- --- --- --- --- --- --- --- ---
    Catering 0 0 2 1 0 0 0 0 0 0 0 0
    Stationary 0 0 1 1 0 0 0 0 0 0 0 0
    Video equipment 0 0 1 1 1 0 0 0 1 0 0 0
    Audio equipment 0 0 0 0 0 0 0 0 0 1 0 0
    Computer equipment 0 0 1 0 0 0 0 0 0 0 0 0
    Locations 0 0 2 2 2 1 1 1 1 1 0 0
    6 rows selected

    Oracle 11g introduced pivot queries.

    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
    SELECT * FROM
    ( SELECT COUNT(*) c, t.name, TO_CHAR(start_date,'mon') mth
    FROM events e, bookings b, resources r, resource_types t
    WHERE e.event_no = b.event_no
    AND r.code = b.resource_code
    AND r.type_code = t.code
    GROUP BY t.name, to_char(start_date,'mon')
    )
    PIVOT
    (SUM(c) -- Add up all my counts
    FOR mth -- Transposing the months
    IN ('jan' as jan
    ,'feb','mar','apr','may','jun'
    ,'jul','aug','sep','oct','nov','dec')
    );
    NAME JAN 'feb' 'mar' 'apr' 'may' 'jun' 'jul' 'aug' 'sep' 'oct' 'nov' 'dec'
    -------------------- --- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----
    Catering 2 1
    Stationary 1 1
    Video equipment 1 1 1 1
    Audio equipment 1
    Computer equipment 1
    Locations 2 2 2 1 1 1 1 1
    6 rows selected
    Note line 12 where we can provide column aliases to the fresh output.

    As with most esoteric SQL functions, there are quite a few good examples on the web that I'm not out to emulate - the prime purpose of this post was to help remind me what's going on.
    That being said, Tim Hall and Arup Nanda have the most concise articles.

    I particularly like Lucas Jellema's example linked by Arup using
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    select value
    from
    (
    (
    select
    'a' v1,
    'e' v2,
    'i' v3,
    'o' v4,
    'u' v5
    from dual
    )
    unpivot
    (
    value
    for value_type in
    (v1,v2,v3,v4,v5)
    )
    )
    /
    I wonder if that might be an interesting catalyst in some future queries

     

    출처 : http://www.grassroots-oracle.com/2012/05/oracle-pivot.html 

    Posted by 1010
    02.Oracle/DataBase2012. 9. 21. 05:30
    반응형

    pivot and unpivot queries in 11g

    Pivot queries involve transposing rows into columns (pivot) or columns into rows (unpivot) to generate results in crosstab format. Pivoting is a common technique, especially for reporting, and it has been possible to generate pivoted resultsets with SQL for many years and Oracle versions. However, the release of 11g includes explicit pivot-query support for the first time with the introduction of the new PIVOT and UNPIVOT keywords. These are extensions to the SELECT statement and we will explore the syntax and application of these new features in this article.

    pivot

    We will begin with the new PIVOT operation. Most developers will be familiar with pivoting data: it is where multiple rows are aggregated and transposed into columns, with each column representing a different range of aggregate data. An overview of the new syntax is as follows:

    SELECT ...
    FROM   ...
    PIVOT [XML]
       ( pivot_clause
         pivot_for_clause
         pivot_in_clause )
    WHERE  ...
    

    In addition to the new PIVOT keyword, we can see three new pivot clauses, described below.

    • pivot_clause: defines the columns to be aggregated (pivot is an aggregate operation);
    • pivot_for_clause: defines the columns to be grouped and pivoted;
    • 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).

    The syntax and mechanics of pivot queries will become clearer with some examples.

    a simple example

    Our first example will be a simple demonstration of the PIVOT syntax. Using the EMP table, we will sum the salaries by department and job, but transpose the sum for each department onto its own column. Before we pivot the salaries, we will examine the base data, as follows.

    SQL> SELECT job
      2  ,      deptno
      3  ,      SUM(sal) AS sum_sal
      4  FROM   emp
      5  GROUP  BY
      6         job
      7  ,      deptno
      8  ORDER  BY
      9         job
     10  ,      deptno;
    
    
    JOB           DEPTNO    SUM_SAL
    --------- ---------- ----------
    ANALYST           20       6600
    CLERK             10       1430
    CLERK             20       2090
    CLERK             30       1045
    MANAGER           10       2695
    MANAGER           20     3272.5
    MANAGER           30       3135
    PRESIDENT         10       5500
    SALESMAN          30       6160
    
    9 rows selected.
    

    We will now pivot this data using the new 11g syntax. For each job, we will display the salary totals in a separate column for each department, as follows.

    SQL> WITH pivot_data AS (
      2          SELECT deptno, job, sal
      3          FROM   emp
      4          )
      5  SELECT *
      6  FROM   pivot_data
      7  PIVOT (
      8             SUM(sal)        --<-- pivot_clause
      9         FOR deptno          --<-- pivot_for_clause
     10         IN  (10,20,30,40)   --<-- pivot_in_clause
     11        );
    
    
    JOB               10         20         30         40
    --------- ---------- ---------- ---------- ----------
    CLERK           1430       2090       1045
    SALESMAN                              6160
    PRESIDENT       5500
    MANAGER         2695     3272.5       3135
    ANALYST                    6600
    
    5 rows selected.
    

    We can see that the department salary totals for each job have been transposed into columns. There are a few points to note about this example, the syntax and the results:

    • Line 8: our pivot_clause sums the SAL column. We can specify multiple columns if required and optionally alias them (we will see examples of aliasing later in this article);
    • Lines 1-4: pivot operations perform an implicit GROUP BY using any columns not in the pivot_clause (in our example, JOB and DEPTNO). For this reason, most pivot queries will be performed on a subset of columns, using stored views, inline views or subqueries, as in our example;
    • Line 9: our pivot_for_clause states that we wish to pivot the DEPTNO aggregations only;
    • Line 10: our pivot_in_clause specifies the range of values for DEPTNO. In this example we have hard-coded a list of four values which is why we generated four pivoted columns (one for each value of DEPTNO). In the absence of aliases, Oracle uses the values in the pivot_in_clause to generate the pivot column names (in our output we can see columns named "10", "20", "30" and "40").

    It was stated above that most pivot queries will be performed on a specific subset of columns. Like all aggregate queries, the presence of additional columns affects the groupings. We can see this quite simply with a pivot query over additional EMP columns as follows.

    SQL> SELECT *
      2  FROM   emp
      3  PIVOT (SUM(sal)
      4  FOR    deptno IN (10,20,30,40));
    
         EMPNO ENAME      JOB              MGR HIREDATE         COMM         10         20         30         40
    ---------- ---------- --------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
          7654 MARTIN     SALESMAN        7698 28/09/1981       1400                             1375
          7698 BLAKE      MANAGER         7839 01/05/1981                                        3135
          7934 MILLER     CLERK           7782 23/01/1982                  1430
          7521 WARD       SALESMAN        7698 22/02/1981        500                             1375
          7566 JONES      MANAGER         7839 02/04/1981                           3272.5
          7844 TURNER     SALESMAN        7698 08/09/1981          0                             1650
          7900 JAMES      CLERK           7698 03/12/1981                                        1045
          7839 KING       PRESIDENT            17/11/1981                  5500
          7876 ADAMS      CLERK           7788 23/05/1987                             1210
          7902 FORD       ANALYST         7566 03/12/1981                             3300
          7788 SCOTT      ANALYST         7566 19/04/1987                             3300
          7782 CLARK      MANAGER         7839 09/06/1981                  2695
          7369 SMITH      CLERK           7902 17/12/1980                              880
          7499 ALLEN      SALESMAN        7698 20/02/1981        300                             1760
    
    14 rows selected.
    

    In this case, all the EMP columns apart from SAL have become the grouping set, with DEPTNO being the pivot column. The pivot is effectively useless in this case.

    An interesting point about the pivot syntax is its placement in the query; namely, between the FROM and WHERE clauses. In the following example, we restrict our original pivot query to a selection of job titles by adding a predicate.

    SQL> WITH pivot_data AS (
      2          SELECT deptno, job, sal
      3          FROM   emp
      4          )
      5  SELECT *
      6  FROM   pivot_data
      7  PIVOT (
      8             SUM(sal)        --<-- pivot_clause
      9         FOR deptno          --<-- pivot_for_clause
     10         IN  (10,20,30,40)   --<-- pivot_in_clause
     11        )
     12  WHERE  job IN ('ANALYST','CLERK','SALESMAN');
    
    
    JOB                10         20         30         40
    ---------- ---------- ---------- ---------- ----------
    CLERK            1430       2090       1045
    SALESMAN                               6160
    ANALYST                     6600
    
    3 rows selected.
    

    This appears to be counter-intuitive, but adding the predicates before the pivot clause raises a syntax error. As an aside, in our first example we used subquery factoring (the WITH clause) to define the base column set. We can alternatively use an inline-view (as follows) or a stored view (we will do this later).

    SQL> SELECT *
      2  FROM  (
      3         SELECT deptno, job, sal
      4         FROM   emp
      5        )
      6  PIVOT (SUM(sal)
      7  FOR    deptno IN (10,20,30,40));
    
    
    JOB               10         20         30         40
    --------- ---------- ---------- ---------- ----------
    CLERK           1430       2090       1045
    SALESMAN                              6160
    PRESIDENT       5500
    MANAGER         2695     3272.5       3135
    ANALYST                    6600
    
    5 rows selected.
    

    aliasing pivot columns

    In our preceding examples, Oracle used the values of DEPTNO to generate pivot column names. Alternatively, we can alias one or more of the columns in the pivot_clause and one or more of the values in the pivot_in_clause. In general, Oracle will name the pivot columns according to the following conventions:

    Pivot Column Aliased? Pivot In-Value Aliased? Pivot Column Name
    N N pivot_in_clause value
    Y Y pivot_in_clause alias || '_' || pivot_clause alias
    N Y pivot_in_clause alias
    Y N pivot_in_clause value || '_' || pivot_clause alias

    We will see examples of each of these aliasing options below (we have already seen examples without any aliases). However, to simplify our examples, we will begin by defining the input dataset as a view, as follows.

    SQL> CREATE VIEW pivot_data
      2  AS
      3     SELECT deptno, job, sal
      4     FROM   emp;
    
    
    View created.
    

    For our first example, we will alias all elements of our pivot query.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT (SUM(sal) AS salaries
      4  FOR    deptno IN (10 AS d10_sal,
      5                    20 AS d20_sal,
      6                    30 AS d30_sal,
      7                    40 AS d40_sal));
    
    
    JOB        D10_SAL_SALARIES D20_SAL_SALARIES D30_SAL_SALARIES D40_SAL_SALARIES
    ---------- ---------------- ---------------- ---------------- ----------------
    CLERK                  1430             2090             1045
    SALESMAN                                                 6160
    PRESIDENT              5500
    MANAGER                2695           3272.5             3135
    ANALYST                                 6600
    
    5 rows selected.
    

    Oracle concatenates our aliases together to generate the column names. In the following example, we will alias the pivot_clause (aggregated column) but not the values in the pivot_in_clause.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT (SUM(sal) AS salaries
      4  FOR    deptno IN (10, 20, 30, 40));
    
    
    JOB       10_SALARIES 20_SALARIES 30_SALARIES 40_SALARIES
    --------- ----------- ----------- ----------- -----------
    CLERK            1430        2090        1045
    SALESMAN                                 6160
    PRESIDENT        5500
    MANAGER          2695      3272.5        3135
    ANALYST                      6600
    
    5 rows selected.
    

    Oracle generates the pivot column names by concatenating the pivot_in_clause values and the aggregate column alias. Finally, we will only alias the pivot_in_clause values, as follows.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT (SUM(sal)
      4  FOR    deptno IN (10 AS d10_sal,
      5                    20 AS d20_sal,
      6                    30 AS d30_sal,
      7                    40 AS d40_sal));
    
    
    JOB           D10_SAL    D20_SAL    D30_SAL    D40_SAL
    ---------- ---------- ---------- ---------- ----------
    CLERK            1430       2090       1045
    SALESMAN                               6160
    PRESIDENT        5500
    MANAGER          2695     3272.5       3135
    ANALYST                     6600
    
    5 rows selected.
    

    This time, Oracle generated column names from the aliases only. In fact, we can see from all of our examples that the pivot_in_clause is used in all pivot-column naming, regardless of whether we supply an alias or value. We can therefore be selective about which values we alias, as the following example demonstrates.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT (SUM(sal)
      4  FOR    deptno IN (10 AS d10_sal,
      5                    20,
      6                    30 AS d30_sal,
      7                    40));
    
    
    JOB          D10_SAL         20    D30_SAL         40
    --------- ---------- ---------- ---------- ----------
    CLERK           1430       2090       1045
    SALESMAN                              6160
    PRESIDENT       5500
    MANAGER         2695     3272.5       3135
    ANALYST                    6600
    
    5 rows selected.
    

    pivoting multiple columns

    Our examples so far have contained a single aggregate and a single pivot column, although we can define more if we wish. In the following example we will define two aggregations in our pivot_clause for the same range of DEPTNO values that we have used so far. The new aggregate is a count of the salaries that comprise the sum.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT (SUM(sal)   AS sum
      4  ,      COUNT(sal) AS cnt
      5  FOR    deptno IN (10 AS d10_sal,
      6                    20 AS d20_sal,
      7                    30 AS d30_sal,
      8                    40 AS d40_sal));
    
    JOB        D10_SAL_SUM D10_SAL_CNT D20_SAL_SUM D20_SAL_CNT D30_SAL_SUM D30_SAL_CNT D40_SAL_SUM D40_SAL_CNT
    ---------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
    CLERK             1430           1        2090           2        1045           1                       0
    SALESMAN                         0                       0        6160           4                       0
    PRESIDENT         5500           1                       0                       0                       0
    MANAGER           2695           1      3272.5           1        3135           1                       0
    ANALYST                          0        6600           2                       0                       0
    
    5 rows selected.
    

    We have doubled the number of pivot columns (because we doubled the number of aggregates). The number of pivot columns is a product of the number of aggregates and the distinct number of values in the pivot_in_clause. In the following example, we will extend the pivot_for_clause and pivot_in_clause to include values for JOB in the filter.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT (SUM(sal)   AS sum
      4  ,      COUNT(sal) AS cnt
      5  FOR   (deptno,job) IN ((30, 'SALESMAN') AS d30_sls,
      6                         (30, 'MANAGER')  AS d30_mgr,
      7                         (30, 'CLERK')    AS d30_clk));
    
    
    D30_SLS_SUM D30_SLS_CNT D30_MGR_SUM D30_MGR_CNT D30_CLK_SUM D30_CLK_CNT
    ----------- ----------- ----------- ----------- ----------- -----------
           6160           4        3135           1        1045           1
    
    1 row selected.
    

    We have limited the query to just 3 jobs within department 30. Note how the pivot_for_clause columns (DEPTNO and JOB) combine to make a single pivot dimension. The aliases we use apply to the combined value domain (for example, "D30_SLS" to represent SALES in department 30).

    Finally, because we know the pivot column-naming rules, we can reference them directly, as follows.

    SQL> SELECT d30_mgr_sum
      2  ,      d30_clk_cnt
      3  FROM   pivot_data
      4  PIVOT (SUM(sal)   AS sum
      5  ,      COUNT(sal) AS cnt
      6  FOR   (deptno,job) IN ((30, 'SALESMAN') AS d30_sls,
      7                         (30, 'MANAGER')  AS d30_mgr,
      8                         (30, 'CLERK')    AS d30_clk));
    
    
    D30_MGR_SUM D30_CLK_CNT
    ----------- -----------
           3135           1
    
    1 row selected.
    

    general restrictions

    There are a few simple "gotchas" to be aware of with pivot queries. For example, we cannot project the column(s) used in the pivot_for_clause (DEPTNO in most of our examples). This is to be expected. The column(s) in the pivot_for_clause are grouped according to the range of values we supply with the pivot_in_clause. In the following example, we will attempt to project the DEPTNO column.

    SQL> SELECT deptno
      2  FROM   emp
      3  PIVOT (SUM(sal)
      4  FOR    deptno IN (10,20,30,40));
    
    SELECT deptno
           *
    ERROR at line 1:
    ORA-00904: "DEPTNO": invalid identifier
    

    Oracle raises an ORA-00904 exception. In this case the DEPTNO column is completely removed from the projection and Oracle tells us that it doesn't exist in this scope. Similarly, we cannot include any column(s) used in the pivot_clause, as the following example demonstrates.

    SQL> SELECT sal
      2  FROM   emp
      3  PIVOT (SUM(sal)
      4  FOR    deptno IN (10,20,30,40));
    
    SELECT sal
           *
    ERROR at line 1:
    ORA-00904: "SAL": invalid identifier
    

    We attempted to project the SAL column but Oracle raised the same exception. This is also to be expected: the pivot_clause defines our aggregations. This also means, of course, that we must use aggregate functions in the pivot_clause. In the following example, we will attempt to define a pivot_clause with a single-group column.

    SQL> SELECT *
      2  FROM   emp
      3  PIVOT (sal
      4  FOR    deptno IN (10,20,30,40));
    
    PIVOT (sal AS salaries
           *
    ERROR at line 3:
    ORA-56902: expect aggregate function inside pivot operation
    

    Oracle raises a new ORA-56902 exception: the error message numbers are getting much higher with every release!

    execution plans for pivot operations

    As we have stated, pivot operations imply a GROUP BY, but we don't need to specify it. We can investigate this by explaining one of our pivot query examples, as follows. We will use Autotrace for convenience (Autotrace uses EXPLAIN PLAN and DBMS_XPLAN to display theoretical execution plans).

    SQL> set autotrace traceonly explain
    
    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT (SUM(sal)
      4  FOR    deptno IN (10 AS d10_sal,
      5                    20 AS d20_sal,
      6                    30 AS d30_sal,
      7                    40 AS d40_sal));
    
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1475541029
    
    ----------------------------------------------------------------------------
    | Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------
    |   0 | SELECT STATEMENT    |      |     5 |    75 |     4  (25)| 00:00:01 |
    |   1 |  HASH GROUP BY PIVOT|      |     5 |    75 |     4  (25)| 00:00:01 |
    |   2 |   TABLE ACCESS FULL | EMP  |    14 |   210 |     3   (0)| 00:00:01 |
    ----------------------------------------------------------------------------
    

    The plan output tells us that this query uses a HASH GROUP BY PIVOT operation. The HASH GROUP BY is a feature of 10g Release 2, but the PIVOT extension is new to 11g. Pivot queries do not automatically generate a PIVOT plan, however. In the following example, we will limit the domain of values in our pivot_in_clause and use Autotrace to explain the query again.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT (SUM(sal)   AS sum
      4  ,      COUNT(sal) AS cnt
      5  FOR   (deptno,job) IN ((30, 'SALESMAN') AS d30_sls,
      6                         (30, 'MANAGER')  AS d30_mgr,
      7                         (30, 'CLERK')    AS d30_clk));
    
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1190005124
    
    ----------------------------------------------------------------------------
    | Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------
    |   0 | SELECT STATEMENT    |      |     1 |    78 |     3   (0)| 00:00:01 |
    |   1 |  VIEW               |      |     1 |    78 |     3   (0)| 00:00:01 |
    |   2 |   SORT AGGREGATE    |      |     1 |    15 |            |          |
    |   3 |    TABLE ACCESS FULL| EMP  |    14 |   210 |     3   (0)| 00:00:01 |
    ----------------------------------------------------------------------------
    

    This time the CBO has costed a simple aggregation over a group by with pivot. It has correctly identified that only one record will be returned from this query, so the GROUP BY operation is unnecessary. Finally, we will explain our first pivot example but use the extended formatting options of DBMS_XPLAN to reveal more information about the work that Oracle is doing.

    SQL> EXPLAIN PLAN SET STATEMENT_ID = 'PIVOT'
      2  FOR
      3     SELECT *
      4     FROM   pivot_data
      5     PIVOT (SUM(sal)
      6     FOR    deptno IN (10 AS d10_sal,
      7                       20 AS d20_sal,
      8                       30 AS d30_sal,
      9                       40 AS d40_sal));
    
    
    Explained.
    
    
    SQL> SELECT *
      2  FROM   TABLE(
      3            DBMS_XPLAN.DISPLAY(
      4               NULL, 'PIVOT', 'TYPICAL +PROJECTION'));
    
    
    PLAN_TABLE_OUTPUT
    ----------------------------------------------------------------------------
    Plan hash value: 1475541029
    
    ----------------------------------------------------------------------------
    | Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------
    |   0 | SELECT STATEMENT    |      |     5 |    75 |     4  (25)| 00:00:01 |
    |   1 |  HASH GROUP BY PIVOT|      |     5 |    75 |     4  (25)| 00:00:01 |
    |   2 |   TABLE ACCESS FULL | EMP  |    14 |   210 |     3   (0)| 00:00:01 |
    ----------------------------------------------------------------------------
    
    Column Projection Information (identified by operation id):
    -----------------------------------------------------------
    
       1 - (#keys=1) "JOB"[VARCHAR2,9], SUM(CASE  WHEN ("DEPTNO"=10) THEN
           "SAL" END )[22], SUM(CASE  WHEN ("DEPTNO"=20) THEN "SAL" END )[22],
           SUM(CASE  WHEN ("DEPTNO"=30) THEN "SAL" END )[22], SUM(CASE  WHEN
           ("DEPTNO"=40) THEN "SAL" END )[22]
       2 - "JOB"[VARCHAR2,9], "SAL"[NUMBER,22], "DEPTNO"[NUMBER,22]
    
    18 rows selected.
    

    DBMS_XPLAN optionally exposes the column projection information contained in PLAN_TABLE for each step of a query. The projection for ID=2 shows the base columns that we select in the PIVOT_DATA view over EMP. The interesting information, however, is for ID=1 (this step is our pivot operation). This clearly shows how Oracle is generating the pivot columns. Many developers will be familiar with this form of SQL: it is how we write pivot queries in versions prior to 11g. Oracle has chosen a CASE expression, but we commonly use DECODE for brevity, as follows.

    SQL> SELECT job
      2  ,      SUM(DECODE(deptno,10,sal)) AS "D10_SAL"
      3  ,      SUM(DECODE(deptno,20,sal)) AS "D20_SAL"
      4  ,      SUM(DECODE(deptno,30,sal)) AS "D30_SAL"
      5  ,      SUM(DECODE(deptno,40,sal)) AS "D40_SAL"
      6  FROM   emp
      7  GROUP  BY
      8         job;
    
    
    JOB          D10_SAL    D20_SAL    D30_SAL    D40_SAL
    --------- ---------- ---------- ---------- ----------
    CLERK           1430       2090       1045
    SALESMAN                              6160
    PRESIDENT       5500
    MANAGER         2695     3272.5       3135
    ANALYST                    6600
    
    5 rows selected.
    

    pivot performance

    From the evidence we have seen, it appears as though Oracle implements the new PIVOT syntax using a recognised SQL format. It follows that we should expect the same performance for our pivot queries regardless of the technique we use (in other words the 11g PIVOT syntax will perform the same as the SUM(DECODE...) pivot technique. We will test this proposition with a larger dataset using Autotrace (for general I/O patterns) and the wall-clock (for elapsed time). First we will create a table with one million rows, as follows.

    SQL> CREATE TABLE million_rows
      2  NOLOGGING
      3  AS
      4     SELECT MOD(TRUNC(DBMS_RANDOM.VALUE(1,10000)),4) AS pivoting_col
      5     ,      MOD(ROWNUM,4)+10                         AS grouping_col
      6     ,      DBMS_RANDOM.VALUE                        AS summing_col
      7     ,      RPAD('X',70,'X')                         AS padding_col
      8     FROM   dual
      9     CONNECT BY ROWNUM <= 1000000;
    
    
    Table created.
    

    We will now compare the two pivot query techniques (after full-scanning the MILLION_ROWS table a couple of times). We will begin with the new 11g syntax, as follows.

    SQL> set timing on
    
    SQL> set autotrace on
    
    SQL> WITH pivot_data AS (
      2          SELECT pivoting_col
      3          ,      grouping_col
      4          ,      summing_col
      5          FROM   million_rows
      6          )
      7  SELECT *
      8  FROM   pivot_data
      9  PIVOT (SUM(summing_col) AS sum
     10  FOR    pivoting_col IN (0,1,2,3))
     11  ORDER  BY
     12         grouping_col;
    
    
    GROUPING_COL      0_SUM      1_SUM      2_SUM      3_SUM
    ------------ ---------- ---------- ---------- ----------
              10 31427.0128 31039.5026 31082.0382 31459.7873
              11 31385.2582 31253.2246 31030.7518 31402.1794
              12 31353.1321  31220.078 31174.0103 31140.5322
              13 31171.1977  30979.714 31486.7785 31395.6907
    
    4 rows selected.
    
    Elapsed: 00:00:04.50
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1201564532
    
    ------------------------------------------------------------------------------------
    | Id  | Operation           | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT    |              |  1155K|    42M|  3978   (2)| 00:00:48 |
    |   1 |  SORT GROUP BY PIVOT|              |  1155K|    42M|  3978   (2)| 00:00:48 |
    |   2 |   TABLE ACCESS FULL | MILLION_ROWS |  1155K|    42M|  3930   (1)| 00:00:48 |
    ------------------------------------------------------------------------------------
    
    Note
    -----
       - dynamic sampling used for this statement
    
    
    Statistics
    ----------------------------------------------------------
            170  recursive calls
              0  db block gets
          14393  consistent gets
          14286  physical reads
              0  redo size
           1049  bytes sent via SQL*Net to client
            416  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              6  sorts (memory)
              0  sorts (disk)
              4  rows processed
    

    The most important outputs are highlighted. We can see that the query completed in 4.5 seconds and generated approximately 14,000 PIOs and LIOs. Interestingly, the CBO chose a SORT GROUP BY over a HASH GROUP BY for this volume, having estimated almost 1.2 million records.

    By way of comparison, we will run the pre-11g version of pivot, as follows.

    SQL> SELECT grouping_col
      2  ,      SUM(DECODE(pivoting_col,0,summing_col)) AS "0_SUM"
      3  ,      SUM(DECODE(pivoting_col,1,summing_col)) AS "1_SUM"
      4  ,      SUM(DECODE(pivoting_col,2,summing_col)) AS "2_SUM"
      5  ,      SUM(DECODE(pivoting_col,3,summing_col)) AS "3_SUM"
      6  FROM   million_rows
      7  GROUP  BY
      8         grouping_col
      9  ORDER  BY
     10         grouping_col;
    
    
    GROUPING_COL      0_SUM      1_SUM      2_SUM      3_SUM
    ------------ ---------- ---------- ---------- ----------
              10 31427.0128 31039.5026 31082.0382 31459.7873
              11 31385.2582 31253.2246 31030.7518 31402.1794
              12 31353.1321  31220.078 31174.0103 31140.5322
              13 31171.1977  30979.714 31486.7785 31395.6907
    
    4 rows selected.
    
    Elapsed: 00:00:04.37
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 2855194314
    
    -----------------------------------------------------------------------------------
    | Id  | Operation          | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
    -----------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT   |              |  1155K|    42M|  3978   (2)| 00:00:48 |
    |   1 |  SORT GROUP BY     |              |  1155K|    42M|  3978   (2)| 00:00:48 |
    |   2 |   TABLE ACCESS FULL| MILLION_ROWS |  1155K|    42M|  3930   (1)| 00:00:48 |
    -----------------------------------------------------------------------------------
    
    Note
    -----
       - dynamic sampling used for this statement
    
    
    Statistics
    ----------------------------------------------------------
              4  recursive calls
              0  db block gets
          14374  consistent gets
          14286  physical reads
              0  redo size
           1049  bytes sent via SQL*Net to client
            416  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              1  sorts (memory)
              0  sorts (disk)
              4  rows processed
    

    With a couple of minor exceptions, the time and resource results for this query are the same as for the new PIVOT syntax. This is as we expected given the internal query re-write we saw earlier. In fact, the new PIVOT version of this query generated more recursive SQL and more in-memory sorts, but we can conclude from this simple test that there is no performance penalty with the new technique. We will test this conclusion with a higher number of pivot columns, as follows.

    SQL> set timing on
    
    SQL> set autotrace traceonly statistics
    
    SQL> WITH pivot_data AS (
      2          SELECT pivoting_col
      3          ,      grouping_col
      4          ,      summing_col
      5          FROM   million_rows
      6          )
      7  SELECT *
      8  FROM   pivot_data
      9  PIVOT (SUM(summing_col)   AS sum
     10  ,      COUNT(summing_col) AS cnt
     11  ,      AVG(summing_col)   AS av
     12  ,      MIN(summing_col)   AS mn
     13  ,      MAX(summing_col)   AS mx
     14  FOR    pivoting_col IN (0,1,2,3))
     15  ORDER  BY
     16         grouping_col;
    
    
    4 rows selected.
    
    Elapsed: 00:00:04.29
    
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
          14290  consistent gets
          14286  physical reads
              0  redo size
           2991  bytes sent via SQL*Net to client
            416  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              1  sorts (memory)
              0  sorts (disk)
              4  rows processed
    

    We have generated 20 pivot columns with this example. Note that the above output is from a third or fourth run of the example to avoid skew in the results. Ultimately, the I/O patterns and elapsed time are the same as our original example, despite pivoting an additional 16 columns. We will compare this with the SUM(DECODE...) technique, as follows.

    SQL> SELECT grouping_col
      2  ,      SUM(DECODE(pivoting_col,0,summing_col))   AS "0_SUM"
      3  ,      COUNT(DECODE(pivoting_col,0,summing_col)) AS "0_CNT"
      4  ,      AVG(DECODE(pivoting_col,0,summing_col))   AS "0_AV"
      5  ,      MIN(DECODE(pivoting_col,0,summing_col))   AS "0_MN"
      6  ,      MAX(DECODE(pivoting_col,0,summing_col))   AS "0_MX"
      7         --
      8  ,      SUM(DECODE(pivoting_col,1,summing_col))   AS "1_SUM"
      9  ,      COUNT(DECODE(pivoting_col,1,summing_col)) AS "1_CNT"
     10  ,      AVG(DECODE(pivoting_col,1,summing_col))   AS "1_AV"
     11  ,      MIN(DECODE(pivoting_col,1,summing_col))   AS "1_MN"
     12  ,      MAX(DECODE(pivoting_col,1,summing_col))   AS "1_MX"
     13         --
     14  ,      SUM(DECODE(pivoting_col,2,summing_col))   AS "2_SUM"
     15  ,      COUNT(DECODE(pivoting_col,2,summing_col)) AS "2_CNT"
     16  ,      AVG(DECODE(pivoting_col,2,summing_col))   AS "2_AV"
     17  ,      MIN(DECODE(pivoting_col,2,summing_col))   AS "2_MN"
     18  ,      MAX(DECODE(pivoting_col,2,summing_col))   AS "2_MX"
     19         --
     20  ,      SUM(DECODE(pivoting_col,3,summing_col))   AS "3_SUM"
     21  ,      COUNT(DECODE(pivoting_col,3,summing_col)) AS "3_CNT"
     22  ,      AVG(DECODE(pivoting_col,3,summing_col))   AS "3_AV"
     23  ,      MIN(DECODE(pivoting_col,3,summing_col))   AS "3_MN"
     24  ,      MAX(DECODE(pivoting_col,3,summing_col))   AS "3_MX"
     25  FROM   million_rows
     26  GROUP  BY
     27         grouping_col
     28  ORDER  BY
     29         grouping_col;
    
    
    4 rows selected.
    
    Elapsed: 00:00:05.12
    
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
          14290  consistent gets
          14286  physical reads
              0  redo size
           2991  bytes sent via SQL*Net to client
            416  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              1  sorts (memory)
              0  sorts (disk)
              4  rows processed
    

    We can begin to see how much more convenient the new PIVOT syntax is. Furthermore, despite the workloads of the two methods being the same, the manual pivot technique is 25% slower (observable over several runs of the same examples and also a version using CASE instead of DECODE).

    pivoting an unknown domain of values

    All of our examples so far have pivoted a known domain of values (in other words, we have used a hard-coded pivot_in_clause). The pivot syntax we have been using doesn't, by default, support a dynamic list of values in the pivot_in_clause. If we use a subquery instead of a list in the pivot_in_clause, as in the following example, Oracle raises a syntax error.

    SQL> SELECT *
      2  FROM   emp
      3  PIVOT (SUM(sal) AS salaries
      4  FOR    deptno IN (SELECT deptno FROM dept));
    
    FOR    deptno IN (SELECT deptno FROM dept))
                      *
    ERROR at line 4:
    ORA-00936: missing expression
    

    Many developers will consider this to be a major restriction (despite the fact that pre-11g pivot techniques also require us to code an explicit set of values). However, it is possible to generate an unknown set of pivot values. Remember from the earlier syntax overview that PIVOT allows an optional "XML" keyword. As the keyword suggests, this enables us to generate a pivot set but have the results provided in XML format. An extension of this is that we can have an XML resultset generated for any number of pivot columns, as defined by a dynamic pivot_in_clause.

    When using the XML extension, we have three options for generating the pivot_in_clause:

    • we can use an explicit list of values (we've been doing this so far in this article);
    • we can use the ANY keyword in the pivot_in_clause. This specifies that we wish to pivot for all values for the columns in the pivot_for_clause; or
    • we can use a subquery in the pivot_in_clause to derive the list of values.

    We will concentrate on the dynamic methods. In the following example, we will use the ANY keyword to generate a pivoted resultset for any values of DEPTNO that we encounter in our dataset.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT  XML
      4        (SUM(sal) FOR deptno IN (ANY));
    
    
    JOB       DEPTNO_XML
    --------- ---------------------------------------------------------------------------
    ANALYST   <PivotSet><item><column name = "DEPTNO">20</column><column name = "SUM(SAL)
              ">6600</column></item></PivotSet>
    
    CLERK     <PivotSet><item><column name = "DEPTNO">10</column><column name = "SUM(SAL)
              ">1430</column></item><item><column name = "DEPTNO">20</column><column name
               = "SUM(SAL)">2090</column></item><item><column name = "DEPTNO">30</column>
              <column name = "SUM(SAL)">1045</column></item></PivotSet>
    
    MANAGER   <PivotSet><item><column name = "DEPTNO">10</column><column name = "SUM(SAL)
              ">2695</column></item><item><column name = "DEPTNO">20</column><column name
               = "SUM(SAL)">3272.5</column></item><item><column name = "DEPTNO">30</colum
              n><column name = "SUM(SAL)">3135</column></item></PivotSet>
    
    PRESIDENT <PivotSet><item><column name = "DEPTNO">10</column><column name = "SUM(SAL)
              ">5500</column></item></PivotSet>
    
    SALESMAN  <PivotSet><item><column name = "DEPTNO">30</column><column name = "SUM(SAL)
              ">6160</column></item></PivotSet>
    
    
    5 rows selected.
    

    The XML resultset is of type XMLTYPE, which means that we can easily manipulate it with XPath or XQuery expressions. We can see that the generated pivot columns are named according to the pivot_clause and not the pivot_in_clause (remember that in the non-XML queries the pivot_in_clause values or aliases featured in all permutations of pivot column-naming). We can also see that the XML column name itself is a product of the pivot_for_clause: Oracle has appended "_XML" to "DEPTNO".

    We will repeat the previous query but add an alias to the pivot_clause, as follows. If we wish to change the column name from "DEPTNO_XML", we use standard SQL column aliasing.

    SQL> SELECT job
      2  ,      deptno_xml AS alias_for_deptno_xml
      3  FROM   pivot_data
      4  PIVOT  XML
      5        (SUM(sal) AS salaries FOR deptno IN (ANY));
    
    
    JOB        ALIAS_FOR_DEPTNO_XML
    ---------- ---------------------------------------------------------------------------
    ANALYST    <PivotSet><item><column name = "DEPTNO">20</column><column name = "SALARIES
               ">6600</column></item></PivotSet>
    
    CLERK      <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES
               ">1430</column></item><item><column name = "DEPTNO">20</column><column name
                = "SALARIES">2090</column></item><item><column name = "DEPTNO">30</column>
               <column name = "SALARIES">1045</column></item></PivotSet>
    
    MANAGER    <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES
               ">2695</column></item><item><column name = "DEPTNO">20</column><column name
                = "SALARIES">3272.5</column></item><item><column name = "DEPTNO">30</colum
               n><column name = "SALARIES">3135</column></item></PivotSet>
    
    PRESIDENT  <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES
               ">5500</column></item></PivotSet>
    
    SALESMAN   <PivotSet><item><column name = "DEPTNO">30</column><column name = "SALARIES
               ">6160</column></item></PivotSet>
    
    
    5 rows selected.
    

    As suggested, the pivot_clause alias defines the pivoted XML element names and the XML column name itself is defined by the projected alias.

    An alternative to the ANY keyword is a subquery. In the following example, we will replace ANY with a query against the DEPT table to derive our list of DEPTNO values.

    SQL> SELECT *
      2  FROM   pivot_data
      3  PIVOT  XML
      4        (SUM(sal) AS salaries FOR deptno IN (SELECT deptno FROM dept));
    
    
    JOB        DEPTNO_XML
    ---------- ---------------------------------------------------------------------------
    ANALYST    <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES
               "></column></item><item><column name = "DEPTNO">20</column><column name = "
               SALARIES">6600</column></item><item><column name = "DEPTNO">30</column><col
               umn name = "SALARIES"></column></item><item><column name = "DEPTNO">40</col
               umn><column name = "SALARIES"></column></item></PivotSet>
    
    CLERK      <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES
               ">1430</column></item><item><column name = "DEPTNO">20</column><column name
                = "SALARIES">2090</column></item><item><column name = "DEPTNO">30</column>
               <column name = "SALARIES">1045</column></item><item><column name = "DEPTNO"
               >40</column><column name = "SALARIES"></column></item></PivotSet>
    
    MANAGER    <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES
               ">2695</column></item><item><column name = "DEPTNO">20</column><column name
                = "SALARIES">3272.5</column></item><item><column name = "DEPTNO">30</colum
               n><column name = "SALARIES">3135</column></item><item><column name = "DEPTN
               O">40</column><column name = "SALARIES"></column></item></PivotSet>
    
    PRESIDENT  <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES
               ">5500</column></item><item><column name = "DEPTNO">20</column><column name
                = "SALARIES"></column></item><item><column name = "DEPTNO">30</column><col
               umn name = "SALARIES"></column></item><item><column name = "DEPTNO">40</col
               umn><column name = "SALARIES"></column></item></PivotSet>
    
    SALESMAN   <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES
               "></column></item><item><column name = "DEPTNO">20</column><column name = "
               SALARIES"></column></item><item><column name = "DEPTNO">30</column><column
               name = "SALARIES">6160</column></item><item><column name = "DEPTNO">40</col
               umn><column name = "SALARIES"></column></item></PivotSet>
    
    
    5 rows selected.
    

    We can see a key difference between this XML output and the resultset from the ANY method. When using the subquery method, Oracle will generate a pivot XML element for every value the subquery returns (one for each grouping). For example, ANALYST employees only work in DEPTNO 20, so the ANY method returns one pivot XML element for that department. The subquery method, however, generates four pivot XML elements (for DEPTNO 10,20,30,40) but only DEPTNO 20 is non-null. We can see this more clearly if we extract the salaries element from both pivot_in_clause methods, as follows.

    SQL> SELECT job
      2  ,      EXTRACT(deptno_xml, '/PivotSet/item/column') AS salary_elements
      3  FROM   pivot_data
      4  PIVOT  XML
      5        (SUM(sal) AS salaries FOR deptno IN (ANY))
      6  WHERE  job = 'ANALYST';
    
    
    JOB       SALARY_ELEMENTS
    --------- ---------------------------------------------------------------------------
    ANALYST   <column name="DEPTNO">20</column><column name="SALARIES">6600</column>
    
    1 row selected.
    

    Using the ANY method, Oracle has generated an XML element for the only DEPTNO (20). We will repeat the query but use the subquery method, as follows.

    SQL> SELECT job
      2  ,      EXTRACT(deptno_xml, '/PivotSet/item/column') AS salary_elements
      3  FROM   pivot_data
      4  PIVOT  XML
      5        (SUM(sal) AS salaries FOR deptno IN (SELECT deptno FROM dept))
      6  WHERE  job = 'ANALYST';
    
    
    JOB       SALARY_ELEMENTS
    --------- ---------------------------------------------------------------------------
    ANALYST   <column name="DEPTNO">10</column><column name="SALARIES"/><column name="DEP
              TNO">20</column><column name="SALARIES">6600</column><column name="DEPTNO">
              30</column><column name="SALARIES"/><column name="DEPTNO">40</column><colum
              n name="SALARIES"/>
    
    
    1 row selected.
    

    Despite the fact that three departments do not have salary totals, Oracle has generated an empty element for each one. Again, only department 20 has a value for salary total. Whichever method developers choose, therefore, depends on requirements, but it is important to recognise that working with XML often leads to inflated dataset or resultset volumes. In this respect, the subquery method can potentially generate a lot of additional data over and above the results themselves.

    unpivot

    We have explored the new 11g pivot capability in some detail above. We will now look at the new UNPIVOT operator. As its name suggests, an unpivot operation is the opposite of pivot (albeit without the ability to disaggregate the data). A simpler way of thinking about unpivot is that it turns pivoted columns into rows (one row of data for every column to be unpivoted). We will see examples of this below, but will start with an overview of the syntax, as follows.

    SELECT ...
    FROM   ...
    UNPIVOT [INCLUDE|EXCLUDE NULLS]
       ( unpivot_clause
         unpivot_for_clause
         unpivot_in_clause )
    WHERE  ...
    

    The syntax is similar to that of PIVOT with some slight differences, including the meaning of the various clauses. These are described as follows:

    • unpivot_clause: this clause specifies a name for a column to represent the unpivoted measure values. In our previous pivot examples, the measure column was the sum of salaries for each job and department grouping;
    • unpivot_for_clause: the unpivot_for_clause specifies the name for the column that will result from our unpivot query. The data in this column describes the measure values in the unpivot_clause column; and
    • unpivot_in_clause: this contains the list of pivoted columns (not values) to be unpivoted.

    The unpivot clauses are quite difficult to describe and are best served by some examples.

    simple unpivot examples

    Before we write an unpivot query, we will create a pivoted dataset to use in our examples. For simplicity, we will create a view using one of our previous pivot queries, as follows.

    SQL> CREATE VIEW pivoted_data
      2  AS
      3     SELECT *
      4     FROM   pivot_data
      5     PIVOT (SUM(sal)
      6     FOR    deptno IN (10 AS d10_sal,
      7                       20 AS d20_sal,
      8                       30 AS d30_sal,
      9                       40 AS d40_sal));
    
    
    View created.
    

    The PIVOTED_DATA view contains our standard sum of department salaries by job, with the four department totals pivoted as we've seen throughout this article. As a final reminder of the nature of the data, we will query this view.

    SQL> SELECT *
      2  FROM   pivoted_data;
    
    
    JOB           D10_SAL    D20_SAL    D30_SAL    D40_SAL
    ---------- ---------- ---------- ---------- ----------
    CLERK            1430       2090       1045
    SALESMAN                               6160
    PRESIDENT        5500
    MANAGER          2695     3272.5       3135
    ANALYST                     6600
    
    5 rows selected.
    

    We will now unpivot our dataset using the new 11g syntax as follows.

    SQL> SELECT *
      2  FROM   pivoted_data
      3  UNPIVOT (
      4               deptsal                              --<-- unpivot_clause
      5           FOR saldesc                              --<-- unpivot_for_clause
      6           IN  (d10_sal, d20_sal, d30_sal, d40_sal) --<-- unpivot_in_clause
      7          );
    
    
    JOB        SALDESC       DEPTSAL
    ---------- ---------- ----------
    CLERK      D10_SAL          1430
    CLERK      D20_SAL          2090
    CLERK      D30_SAL          1045
    SALESMAN   D30_SAL          6160
    PRESIDENT  D10_SAL          5500
    MANAGER    D10_SAL          2695
    MANAGER    D20_SAL        3272.5
    MANAGER    D30_SAL          3135
    ANALYST    D20_SAL          6600
    
    9 rows selected.
    

    We can see from the results that Oracle has transposed each of our pivoted columns in the unpivot_in_clause and turned them into rows of data that describes our measure (i.e. 'D10_SAL', 'D20_SAL' and so on). The unpivot_for_clause gives this new unpivoted column a name (i.e "SALDESC"). The unpivot_clause itself defines our measure data, which in this case is the sum of the department's salary by job.

    It is important to note that unpivot queries can work on any columns (i.e. not just aggregated or pivoted columns). We are using the pivoted dataset for consistency but we could just as easily unpivot the columns of any table or view we have.

    handling null data

    The maximum number of rows that can be returned by an unpivot query is the number of distinct groupings multiplied by the number of pivot columns (in our examples, 5 (jobs) * 4 (pivot columns) = 20). However, our first unpivot query has only returned nine rows. If we look at the source pivot data itself, we can see nine non-null values in the pivot columns; in other words, eleven groupings are null. The default behaviour of UNPIVOT is to exclude nulls, but we do have an option to include them, as follows.

    SQL> SELECT *
      2  FROM   pivoted_data
      3  UNPIVOT INCLUDE NULLS
      4        (deptsal
      5  FOR    saldesc IN (d10_sal,
      6                     d20_sal,
      7                     d30_sal,
      8                     d40_sal));
    
    
    JOB        SALDESC       DEPTSAL
    ---------- ---------- ----------
    CLERK      D10_SAL          1430
    CLERK      D20_SAL          2090
    CLERK      D30_SAL          1045
    CLERK      D40_SAL
    SALESMAN   D10_SAL
    SALESMAN   D20_SAL
    SALESMAN   D30_SAL          6160
    SALESMAN   D40_SAL
    PRESIDENT  D10_SAL          5500
    PRESIDENT  D20_SAL
    PRESIDENT  D30_SAL
    PRESIDENT  D40_SAL
    MANAGER    D10_SAL          2695
    MANAGER    D20_SAL        3272.5
    MANAGER    D30_SAL          3135
    MANAGER    D40_SAL
    ANALYST    D10_SAL
    ANALYST    D20_SAL          6600
    ANALYST    D30_SAL
    ANALYST    D40_SAL
    
    20 rows selected.
    

    By including the null pivot values, we return the maximum number of rows possible from our dataset. Of course, we now have eleven null values, but this might be something we require for reporting purposes or "data densification".

    unpivot aliasing options

    In the pivot section of this article, we saw a wide range of aliasing options. The UNPIVOT syntax also allows us to use aliases, but it is far more restrictive. In fact, we can only alias the columns defined in the unpivot_in_clause, as follows.

    SQL> SELECT job
      2  ,      saldesc
      3  ,      deptsal
      4  FROM   pivoted_data
      5  UNPIVOT (deptsal
      6  FOR      saldesc IN (d10_sal AS 'SAL TOTAL FOR 10',
      7                       d20_sal AS 'SAL TOTAL FOR 20',
      8                       d30_sal AS 'SAL TOTAL FOR 30',
      9                       d40_sal AS 'SAL TOTAL FOR 40'))
     10  ORDER  BY
     11         job
     12  ,      saldesc;
    
    
    JOB        SALDESC                 DEPTSAL
    ---------- -------------------- ----------
    ANALYST    SAL TOTAL FOR 20           6600
    CLERK      SAL TOTAL FOR 10           1430
    CLERK      SAL TOTAL FOR 20           2090
    CLERK      SAL TOTAL FOR 30           1045
    MANAGER    SAL TOTAL FOR 10           2695
    MANAGER    SAL TOTAL FOR 20         3272.5
    MANAGER    SAL TOTAL FOR 30           3135
    PRESIDENT  SAL TOTAL FOR 10           5500
    SALESMAN   SAL TOTAL FOR 30           6160
    
    9 rows selected.
    

    This is a useful option because it enables us to change the descriptive data to something other than its original column name. If we wish to alias the column in the unpivot_clause (in our case, DEPTSAL), we need to use standard column aliasing in the SELECT clause. Of course, aliasing the unpivot_for_clause is irrelevant because we have just defined this derived column name in the clause itself (in our case, "SALDESC").

    general restrictions

    The UNPIVOT syntax can be quite fiddly and there are some minor restrictions to how it can be used. The main restriction is that the columns in the unpivot_in_clause must all be of the same datatype. We will see this below by attempting to unpivot three columns of different datatypes from EMP. The unpivot query itself is meaningless: it is just a means to show the restriction, as follows.

    SQL> SELECT empno
      2  ,      job
      3  ,      unpivot_col_name
      4  ,      unpivot_col_value
      5  FROM   emp
      6  UNPIVOT (unpivot_col_value
      7  FOR      unpivot_col_name
      8  IN      (ename, deptno, hiredate));
    
    IN      (ename, deptno, hiredate))
                    *
    ERROR at line 8:
    ORA-01790: expression must have same datatype as corresponding expression
    

    Oracle is also quite fussy about datatype conversion. In the following example, we will attempt to convert the columns to the same VARCHAR2 datatype.

    SQL> SELECT job
      2  ,      unpivot_col_name
      3  ,      unpivot_col_value
      4  FROM   emp
      5  UNPIVOT (unpivot_col_value
      6  FOR      unpivot_col_name
      7  IN      (ename, TO_CHAR(deptno), TO_CHAR(hiredate)));
    
    IN      (ename, TO_CHAR(deptno), TO_CHAR(hiredate)))
                           *
    ERROR at line 7:
    ORA-00917: missing comma
    

    It appears that using datatype conversions within the unpivot_in_clause is not even valid syntax and Oracle raises an exception accordingly. The workaround is, therefore, to convert the columns up-front, using an in-line view, subquery or a stored view. We will use subquery factoring, as follows.

    SQL> WITH emp_data AS (
      2          SELECT empno
      3          ,      job
      4          ,      ename
      5          ,      TO_CHAR(deptno)   AS deptno
      6          ,      TO_CHAR(hiredate) AS hiredate
      7          FROM   emp
      8          )
      9  SELECT empno
     10  ,      job
     11  ,      unpivot_col_name
     12  ,      unpivot_col_value
     13  FROM   emp_data
     14  UNPIVOT (unpivot_col_value
     15  FOR      unpivot_col_name
     16  IN      (ename, deptno, hiredate));
    
    
         EMPNO JOB        UNPIVOT_COL_NAME     UNPIVOT_COL_VALUE
    ---------- ---------- -------------------- --------------------
          7369 CLERK      ENAME                SMITH
          7369 CLERK      DEPTNO               20
          7369 CLERK      HIREDATE             17/12/1980
          7499 SALESMAN   ENAME                ALLEN
          7499 SALESMAN   DEPTNO               30
          7499 SALESMAN   HIREDATE             20/02/1981
          
          <<...snip...>>
    
          7902 ANALYST    ENAME                FORD
          7902 ANALYST    DEPTNO               20
          7902 ANALYST    HIREDATE             03/12/1981
          7934 CLERK      ENAME                MILLER
          7934 CLERK      DEPTNO               10
          7934 CLERK      HIREDATE             23/01/1982
    
    42 rows selected.
    

    The output has been reduced, but we can see the effect of unpivoting on the EMP data (i.e. we have 3 unpivot columns, 14 original rows and hence 42 output records).

    Another restriction with UNPIVOT is that the columns we include in the unpivot_in_clause are not available to us to project outside of the pivot_clause itself. In the following example, we will try to project the DEPTNO column.

    SQL> WITH emp_data AS (
      2          SELECT empno
      3          ,      job
      4          ,      ename
      5          ,      TO_CHAR(deptno)   AS deptno
      6          ,      TO_CHAR(hiredate) AS hiredate
      7          FROM   emp
      8          )
      9  SELECT empno
     10  ,      job
     11  ,      deptno
     12  ,      unpivot_col_name
     13  ,      unpivot_col_value
     14  FROM   emp_data
     15  UNPIVOT (unpivot_col_value
     16  FOR      unpivot_col_name
     17  IN      (ename, deptno, hiredate));
    
    ,      deptno
           *
    ERROR at line 11:
    ORA-00904: "DEPTNO": invalid identifier
    

    Oracle raises an invalid identifier exception. We can see why this is the case when we project all available columns from our unpivot query over EMP, as follows.

    SQL> WITH emp_data AS (
      2          SELECT empno
      3          ,      job
      4          ,      ename
      5          ,      TO_CHAR(deptno)   AS deptno
      6          ,      TO_CHAR(hiredate) AS hiredate
      7          FROM   emp
      8          )
      9  SELECT *
     10  FROM   emp_data
     11  UNPIVOT (unpivot_col_value
     12  FOR      unpivot_col_name
     13  IN      (ename, deptno, hiredate));
    
    
         EMPNO JOB        UNPIVOT_COL_NAME     UNPIVOT_COL_VALUE
    ---------- ---------- -------------------- --------------------
          7369 CLERK      ENAME                SMITH
          7369 CLERK      DEPTNO               20
          7369 CLERK      HIREDATE             17/12/1980
          
          <<...snip...>>
    
          7934 CLERK      ENAME                MILLER
          7934 CLERK      DEPTNO               10
          7934 CLERK      HIREDATE             23/01/1982
    
    42 rows selected.
    

    We can see that the unpivot columns are not available as part of the projection.

    execution plans for unpivot operations

    Earlier we saw the GROUP BY PIVOT operation in the execution plans for our pivot queries. In the following example, we will use Autotrace to generate an explain plan for our last unpivot query.

    SQL> set autotrace traceonly explain
    
    SQL> SELECT job
      2  ,      saldesc
      3  ,      deptsal
      4  FROM   pivoted_data
      5  UNPIVOT (deptsal
      6  FOR      saldesc IN (d10_sal AS 'SAL TOTAL FOR 10',
      7                       d20_sal AS 'SAL TOTAL FOR 20',
      8                       d30_sal AS 'SAL TOTAL FOR 30',
      9                       d40_sal AS 'SAL TOTAL FOR 40'))
     10  ORDER  BY
     11         job
     12  ,      saldesc;
    
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 1898428924
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation               | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT        |              |    20 |   740 |    17  (30)| 00:00:01 |
    |   1 |  SORT ORDER BY          |              |    20 |   740 |    17  (30)| 00:00:01 |
    |*  2 |   VIEW                  |              |    20 |   740 |    16  (25)| 00:00:01 |
    |   3 |    UNPIVOT              |              |       |       |            |          |
    |   4 |     VIEW                | PIVOTED_DATA |     5 |   290 |     4  (25)| 00:00:01 |
    |   5 |      HASH GROUP BY PIVOT|              |     5 |    75 |     4  (25)| 00:00:01 |
    |   6 |       TABLE ACCESS FULL | EMP          |    14 |   210 |     3   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       2 - filter("unpivot_view"."DEPTSAL" IS NOT NULL)
    

    The points of interest are highlighted. First, we can see a new UNPIVOT step (ID=3). Second, we can see a filter predicate to remove all NULL values for DEPTSAL. This is a result of the default EXCLUDING NULLS clause. If we use the INCLUDING NULLS option, this filter is removed. Note that the GROUP BY PIVOT operation at ID=5 is generated by the pivot query that underlies the PIVOTED_DATA view.

    We will extract some more detailed information about this execution plan by using DBMS_XPLAN's format options, as follows. In particular, we will examine the alias and projection details, to see if it provides any clues about Oracle's implementation of UNPIVOT.

    SQL> EXPLAIN PLAN SET STATEMENT_ID = 'UNPIVOT'
      2  FOR
      3     SELECT job
      4     ,      saldesc
      5     ,      deptsal
      6     FROM   pivoted_data
      7     UNPIVOT (deptsal
      8     FOR      saldesc IN (d10_sal AS 'SAL TOTAL FOR 10',
      9                          d20_sal AS 'SAL TOTAL FOR 20',
     10                          d30_sal AS 'SAL TOTAL FOR 30',
     11                          d40_sal AS 'SAL TOTAL FOR 40'))
     12     ORDER  BY
     13            job
     14     ,      saldesc;
    
    
    Explained.
    
    
    SQL> SELECT *
      2  FROM   TABLE(
      3             DBMS_XPLAN.DISPLAY(
      4                NULL, 'UNPIVOT', 'TYPICAL +PROJECTION +ALIAS'));
    
    
    PLAN_TABLE_OUTPUT
    ----------------------------------------------------------------------------------------
    Plan hash value: 1898428924
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation               | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT        |              |    20 |   740 |    17  (30)| 00:00:01 |
    |   1 |  SORT ORDER BY          |              |    20 |   740 |    17  (30)| 00:00:01 |
    |*  2 |   VIEW                  |              |    20 |   740 |    16  (25)| 00:00:01 |
    |   3 |    UNPIVOT              |              |       |       |            |          |
    |   4 |     VIEW                | PIVOTED_DATA |     5 |   290 |     4  (25)| 00:00:01 |
    |   5 |      HASH GROUP BY PIVOT|              |     5 |    75 |     4  (25)| 00:00:01 |
    |   6 |       TABLE ACCESS FULL | EMP          |    14 |   210 |     3   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Query Block Name / Object Alias (identified by operation id):
    -------------------------------------------------------------
    
       1 - SEL$D50F4D64
       2 - SET$1        / unpivot_view@SEL$17
       3 - SET$1
       4 - SEL$CB31B938 / PIVOTED_DATA@SEL$4
       5 - SEL$CB31B938
       6 - SEL$CB31B938 / EMP@SEL$15
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       2 - filter("unpivot_view"."DEPTSAL" IS NOT NULL)
    
    Column Projection Information (identified by operation id):
    -----------------------------------------------------------
    
      1 - (#keys=2) "unpivot_view"."JOB"[VARCHAR2,9],
           "unpivot_view"."SALDESC"[CHARACTER,16], "unpivot_view"."DEPTSAL"[NUMBER,22]
       2 - "unpivot_view"."JOB"[VARCHAR2,9],
           "unpivot_view"."SALDESC"[CHARACTER,16], "unpivot_view"."DEPTSAL"[NUMBER,22]
       3 - STRDEF[9], STRDEF[16], STRDEF[22]
       4 - "PIVOTED_DATA"."JOB"[VARCHAR2,9], "D10_SAL"[NUMBER,22],
           "PIVOTED_DATA"."D20_SAL"[NUMBER,22], "PIVOTED_DATA"."D30_SAL"[NUMBER,22],
           "PIVOTED_DATA"."D40_SAL"[NUMBER,22]
       5 - (#keys=1) "JOB"[VARCHAR2,9], SUM(CASE  WHEN ("DEPTNO"=10) THEN "SAL" END
           )[22], SUM(CASE  WHEN ("DEPTNO"=20) THEN "SAL" END )[22], SUM(CASE  WHEN
           ("DEPTNO"=30) THEN "SAL" END )[22], SUM(CASE  WHEN ("DEPTNO"=40) THEN "SAL" END
           )[22]
       6 - "JOB"[VARCHAR2,9], "SAL"[NUMBER,22], "DEPTNO"[NUMBER,22]
    
    45 rows selected.
    

    The projection of the unpivoted columns is highlighted between operations 1 and 3 above. This does not really provide any clues to how Oracle implements UNPIVOT. Note that a 10046 trace (SQL trace) provides no clues either, so has been omitted from this article.

    The alias information is slightly more interesting, but still tells us little about UNPIVOT. It might be a red herring, but when Oracle transforms a simple query, the generated alias names for query blocks usually follow a pattern such as "SEL$1", "SEL$2" and so on. In our unpivot query, the aliases are as high as SEL$17, yet this is a relatively simple query with few components. This could suggest that a lot of query re-write is happening before optimisation, but we can't be certain from the details we have.

    other uses for unpivot

    Unpivot queries are not restricted to transposing previously pivoted data. We can pivot any set of columns from a table (within the datatype restriction described earlier). A good example is Tom Kyte's print_table procedure. This utility unpivots wide records to enable us to read the data down the page instead of across. The new UNPIVOT can be used for the same purpose. In the following example, we will write a static unpivot query similar to those that the print_table utility is used for.

    SQL> WITH all_objects_data AS (
      2          SELECT owner
      3          ,      object_name
      4          ,      subobject_name
      5          ,      TO_CHAR(object_id)      AS object_id
      6          ,      TO_CHAR(data_object_id) AS data_object_id
      7          ,      object_type
      8          ,      TO_CHAR(created)        AS created
      9          ,      TO_CHAR(last_ddl_time)  AS last_ddl_time
     10          ,      timestamp
     11          ,      status
     12          ,      temporary
     13          ,      generated
     14          ,      secondary
     15          ,      TO_CHAR(namespace)      AS namespace
     16          ,      edition_name
     17          FROM   all_objects
     18          WHERE  ROWNUM = 1
     19          )
     20  SELECT column_name
     21  ,      column_value
     22  FROM   all_objects_data
     23  UNPIVOT (column_value
     24  FOR      column_name
     25  IN      (owner, object_name, subobject_name, object_id,
     26           data_object_id, object_type, created, last_ddl_time,
     27           timestamp, status, temporary, generated,
     28           secondary, namespace, edition_name));
    
    
    COLUMN_NAME    COLUMN_VALUE
    -------------- ---------------------
    OWNER          SYS
    OBJECT_NAME    ICOL$
    OBJECT_ID      20
    DATA_OBJECT_ID 2
    OBJECT_TYPE    TABLE
    CREATED        15/10/2007 10:09:08
    LAST_DDL_TIME  15/10/2007 10:56:08
    TIMESTAMP      2007-10-15:10:09:08
    STATUS         VALID
    TEMPORARY      N
    GENERATED      N
    SECONDARY      N
    NAMESPACE      1
    
    13 rows selected.
    

    Turning this into a dynamic SQL solution is simple and can be an exercise for the reader.

    unpivot queries prior to 11g

    To complete this article, we will include a couple of techniques for unpivot queries in versions prior to 11g and compare their performance. The first method uses a Cartesian Product with a generated dummy rowsource. This rowsource has the same number of rows as the number of columns we wish to unpivot. Using the same dataset as our UNPIVOT examples, we will demonstrate this below.

    SQL> WITH row_source AS (
      2          SELECT ROWNUM AS rn
      3          FROM   all_objects
      4          WHERE  ROWNUM <= 4
      5          )
      6  SELECT p.job
      7  ,      CASE r.rn
      8            WHEN 1
      9            THEN 'D10_SAL'
     10            WHEN 2
     11            THEN 'D20_SAL'
     12            WHEN 3
     13            THEN 'D30_SAL'
     14            WHEN 4
     15            THEN 'D40_SAL'
     16         END AS saldesc
     17  ,      CASE r.rn
     18            WHEN 1
     19            THEN d10_sal
     20            WHEN 2
     21            THEN d20_sal
     22            WHEN 3
     23            THEN d30_sal
     24            WHEN 4
     25            THEN d40_sal
     26         END AS deptsal
     27  FROM   pivoted_data p
     28  ,      row_source   r
     29  ORDER  BY
     30         p.job
     31  ,      saldesc;
    
    
    JOB        SALDESC       DEPTSAL
    ---------- ---------- ----------
    ANALYST    D10_SAL
    ANALYST    D20_SAL          6600
    ANALYST    D30_SAL
    ANALYST    D40_SAL
    CLERK      D10_SAL          1430
    CLERK      D20_SAL          2090
    CLERK      D30_SAL          1045
    CLERK      D40_SAL
    MANAGER    D10_SAL          2695
    MANAGER    D20_SAL        3272.5
    MANAGER    D30_SAL          3135
    MANAGER    D40_SAL
    PRESIDENT  D10_SAL          5500
    PRESIDENT  D20_SAL
    PRESIDENT  D30_SAL
    PRESIDENT  D40_SAL
    SALESMAN   D10_SAL
    SALESMAN   D20_SAL
    SALESMAN   D30_SAL          6160
    SALESMAN   D40_SAL
    
    20 rows selected.
    

    The resultset is the equivalent of using the new UNPIVOT with the INCLUDING NULLS option. The second technique we can use to unpivot data joins the pivoted dataset to a collection of the columns we wish to transpose. The following example uses a generic NUMBER_NTT nested table type to hold the pivoted department salary columns. We can use a numeric type because all the pivoted columns are of NUMBER. We will create the type as follows.

    SQL> CREATE OR REPLACE TYPE number_ntt AS TABLE OF NUMBER;
      2  /
    
    
    Type created.
    

    Using this collection type for the pivoted department salaries, we will now unpivot the data, as follows.

    SQL> SELECT p.job
      2  ,      s.column_value AS deptsal
      3  FROM   pivoted_data p
      4  ,      TABLE(number_ntt(d10_sal,d20_sal,d30_sal,d40_sal)) s
      5  ORDER  BY
      6         p.job;
    
    
    JOB           DEPTSAL
    ---------- ----------
    ANALYST
    ANALYST          6600
    ANALYST
    ANALYST
    CLERK
    CLERK            1045
    CLERK            1430
    CLERK            2090
    MANAGER        3272.5
    MANAGER
    MANAGER          3135
    MANAGER          2695
    PRESIDENT
    PRESIDENT
    PRESIDENT
    PRESIDENT        5500
    SALESMAN         6160
    SALESMAN
    SALESMAN
    SALESMAN
    
    20 rows selected.
    

    While we have unpivoted the department salaries, we have lost our descriptive labels for each of the values. There is no simple way with this technique to decode a row number (like we did in the Cartesian Product example). We can, however, change the collection type we use to include a descriptor. For this purpose, we will first create a generic object type to define a single row of numeric unpivot data, as follows.

    SQL> CREATE TYPE name_value_ot AS OBJECT
      2  ( name  VARCHAR2(30)
      3  , value NUMBER
      4  );
      5  /
    
    
    Type created.
    

    We will now create a collection type based on this object, as follows.

    SQL> CREATE TYPE name_value_ntt
      2     AS TABLE OF name_value_ot;
      3  /
    
    
    Type created.
    

    We will now repeat our previous unpivot query, but provide descriptions using our new collection type.

    SQL> SELECT p.job
      2  ,      s.name  AS saldesc
      3  ,      s.value AS deptsal
      4  FROM   pivoted_data p
      5  ,      TABLE(
      6            name_value_ntt(
      7               name_value_ot('D10_SAL', d10_sal),
      8               name_value_ot('D20_SAL', d20_sal),
      9               name_value_ot('D30_SAL', d30_sal),
     10               name_value_ot('D40_SAL', d40_sal) )) s
     11  ORDER  BY
     12         p.job
     13  ,      s.name;
    
    
    JOB        SALDESC       DEPTSAL
    ---------- ---------- ----------
    ANALYST    D10_SAL
    ANALYST    D20_SAL          6600
    ANALYST    D30_SAL
    ANALYST    D40_SAL
    CLERK      D10_SAL          1430
    CLERK      D20_SAL          2090
    CLERK      D30_SAL          1045
    CLERK      D40_SAL
    MANAGER    D10_SAL          2695
    MANAGER    D20_SAL        3272.5
    MANAGER    D30_SAL          3135
    MANAGER    D40_SAL
    PRESIDENT  D10_SAL          5500
    PRESIDENT  D20_SAL
    PRESIDENT  D30_SAL
    PRESIDENT  D40_SAL
    SALESMAN   D10_SAL
    SALESMAN   D20_SAL
    SALESMAN   D30_SAL          6160
    SALESMAN   D40_SAL
    
    20 rows selected.
    

    We can see that the new 11g UNPIVOT syntax is easier to use than the pre-11g alternatives. We will also compare the performance of each of these techniques, using Autotrace, the wall-clock and our MILLION_ROWS test table. We will start with the new 11g syntax and unpivot the three numeric columns of our test table, as follows.

    SQL> set autotrace traceonly statistics
    
    SQL> set timing on
    
    SQL> SELECT *
      2  FROM   million_rows
      3  UNPIVOT (column_value
      4  FOR      column_name
      5  IN      (pivoting_col, summing_col, grouping_col));
    
    
    3000000 rows selected.
    
    Elapsed: 00:00:09.51
    
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
          20290  consistent gets
          14286  physical reads
              0  redo size
       80492071  bytes sent via SQL*Net to client
          66405  bytes received via SQL*Net from client
           6001  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
        3000000  rows processed
    

    The 11g UNPIVOT method generated 3 million rows in under 10 seconds with only slightly more logical I/O than in our PIVOT tests. We will compare this with the Cartesian Product method, but using a rowsource technique that generates no additional I/O (instead of the ALL_OBJECTS view that we used previously).

    SQL> WITH row_source AS (
      2          SELECT ROWNUM AS rn
      3          FROM   dual
      4          CONNECT BY ROWNUM <= 3
      5          )
      6  SELECT m.padding_col
      7  ,      CASE r.rn
      8            WHEN 0
      9            THEN 'PIVOTING_COL'
     10            WHEN 1
     11            THEN 'SUMMING_COL'
     12            ELSE 'GROUPING_COL'
     13         END AS column_name
     14  ,      CASE r.rn
     15            WHEN 0
     16            THEN m.pivoting_col
     17            WHEN 1
     18            THEN m.summing_col
     19            ELSE m.grouping_col
     20         END AS column_value
     21  FROM   million_rows m
     22  ,      row_source   r;
     
    
    3000000 rows selected.
    
    Elapsed: 00:00:24.95
    
    Statistics
    ----------------------------------------------------------
            105  recursive calls
              2  db block gets
          14290  consistent gets
          54288  physical reads
              0  redo size
       42742181  bytes sent via SQL*Net to client
          66405  bytes received via SQL*Net from client
           6001  SQL*Net roundtrips to/from client
              1  sorts (memory)
              1  sorts (disk)
        3000000  rows processed
    

    The Cartesian Product method is considerably slower than the new 11g UNPIVOT syntax. It generates considerably more I/O and takes over twice as long (note that these results are repeatable across multiple re-runs). However, investigations with SQL trace indicate that this additional I/O is a result of direct path reads and writes to the temporary tablespace, to support a large buffer sort (i.e. the sort that accompanies a MERGE JOIN CARTESIAN operation). On most commercial systems, this buffer sort will probably be performed entirely in memory or the temporary tablespace access will be quicker. For a small system with slow disk access (such as the 11g database used for this article), it has a large impact on performance. We can tune this to a degree by forcing a nested loop join and/or avoiding the disk sort altogether, as follows.

    SQL> WITH row_source AS (
      2          SELECT ROWNUM AS rn
      3          FROM   dual
      4          CONNECT BY ROWNUM <= 3
      5          )
      6  SELECT /*+ ORDERED USE_NL(r) */
      7         m.padding_col
      8  ,      CASE r.rn
      9            WHEN 0
     10            THEN 'PIVOTING_COL'
     11            WHEN 1
     12            THEN 'SUMMING_COL'
     13            ELSE 'GROUPING_COL'
     14         END AS column_name
     15  ,      CASE r.rn
     16            WHEN 0
     17            THEN m.pivoting_col
     18            WHEN 1
     19            THEN m.summing_col
     20            ELSE m.grouping_col
     21         END AS column_value
     22  FROM   million_rows m
     23  ,      row_source   r;
    
    
    3000000 rows selected.
    
    Elapsed: 00:00:14.17
    
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
          20290  consistent gets
          14286  physical reads
              0  redo size
       64742156  bytes sent via SQL*Net to client
          66405  bytes received via SQL*Net from client
           6001  SQL*Net roundtrips to/from client
        1000000  sorts (memory)
              0  sorts (disk)
        3000000  rows processed
    

    We have significantly reduced the elapsed time and I/O for this method on this database, but have introduced one million tiny sorts. We can easily reverse the nested loops order or use the NO_USE_MERGE hint (which also reverses the NL order), but this doubles the I/O and adds 10% to the elapsed time.

    Moving on, we will finally compare our collection method, as follows.

    SQL> SELECT m.padding_col
      2  ,      t.name  AS column_name
      3  ,      t.value AS column_value
      4  FROM   million_rows m
      5  ,      TABLE(
      6            name_value_ntt(
      7               name_value_ot('PIVOTING_COL', pivoting_col),
      8               name_value_ot('SUMMING_COL',  summing_col),
      9               name_value_ot('GROUPING_COL', grouping_col ))) t;
    
    
    3000000 rows selected.
    
    Elapsed: 00:00:12.84
    
    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
          20290  consistent gets
          14286  physical reads
              0  redo size
       80492071  bytes sent via SQL*Net to client
          66405  bytes received via SQL*Net from client
           6001  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
        3000000  rows processed
    

    This method is comparable in I/O to the new UNPIVOT operation but is approximately 35-40% slower. Further investigation using SQL trace suggests that this is due to additional CPU time spent in the collection iterator fetches. Therefore, the new UNPIVOT operation is both easier to code and quicker to run than its SQL alternatives.

    further reading

    For more information on the new PIVOT and UNPIVOT syntax, see the SQL Reference documentation. For some more examples of the use of pivot and unpivot queries, see the Data Warehousing Guide here and here.

    source code

    The source code for the examples in this article can be downloaded from here.

    Adrian Billington, April 2008

    출처 : http://www.oracle-developer.net/display.php?id=506

    Posted by 1010
    02.Oracle/DataBase2012. 9. 21. 05:23
    반응형

    WITH TEST AS
    (
    select 'A' grade, 10 cnt from dual union all
    select 'A' grade, 20 cnt from dual union all
    select 'A' grade, 15 cnt from dual union all
    select 'B' grade, 20 from dual union all
    select 'B' grade, 30 from dual union all
    select 'D' grade, 15 from dual union all
    select 'F' grade, 10 from dual
    )
    SELECT
    grade 등급,
    cnt 학생수,
    sum(cnt) over () 갯수,
    row_number() over(partition by grade order by cnt) 로넘,
    rank() over(partition by grade order by cnt) 랭크,
    round((cnt / sum(cnt) over ())*100,2) 구성비,
    sum(cnt) over (partition by grade order by grade) 누적학생수,
    sum(cnt) over (partition by grade,cnt order by grade) 그레카운,
    sum(cnt) over (order by grade) 누적,
    round((sum(cnt) over (order by grade)/sum(cnt) over ())*100,2) 누적구성비
    FROM TEST;

    /*

    rank() over(partition by grade order by cnt) 랭크,

    표현식 over (partition by 컬럼1 order by 컬럼2)

    이 의미는

    "(컬럼1값이 같은놈들로 묶은것을 컬럼2로 정렬한) 각 파티션별로 표현식을 수행해준다."

    */

    복잡하군... :(

    [출처] Oracle over() 구문|작성자 18061975

     

    Posted by 1010
    02.Oracle/DataBase2012. 9. 21. 03:19
    반응형

    오랔믈 pivot 기능

    SELECT TO_CHAR(rgs_dt,'YYYYMMDD'),
                  'TA_IPT_ICG_MNFT_MST' AS title,
                  COUNT(mrn)            AS cnt
             FROM TA_IPT_ICG_MNFT_MST
            WHERE USE_FG = 'S'
            GROUP BY TO_CHAR(rgs_dt,'YYYYMMDD')
               UNION
           SELECT TO_CHAR(rgs_dt,'YYYYMMDD'),
                  'TA_ICG_MNFT_MST' AS title,
                  COUNT(mrn)        AS cnt
             FROM TA_ICG_MNFT_MST
            WHERE USE_FG = 'S'
            GROUP BY TO_CHAR(rgs_dt,'YYYYMMDD')

     

     

     

     

    SELECT *
      FROM
           (SELECT TO_CHAR(rgs_dt,'YYYYMMDD'),
                  'TA_IPT_ICG_MNFT_MST' AS title,
                  COUNT(mrn)            AS cnt
             FROM TA_IPT_ICG_MNFT_MST
            WHERE USE_FG = 'S'
            GROUP BY TO_CHAR(rgs_dt,'YYYYMMDD')
               UNION
           SELECT TO_CHAR(rgs_dt,'YYYYMMDD'),
                  'TA_ICG_MNFT_MST' AS title,
                  COUNT(mrn)        AS cnt
             FROM TA_ICG_MNFT_MST
            WHERE USE_FG = 'S'
            GROUP BY TO_CHAR(rgs_dt,'YYYYMMDD')
           ) pivot (SUM(cnt) FOR title IN('TA_IPT_ICG_MNFT_MST','TA_ICG_MNFT_MST'))
    ORDER BY 1

     

     

     

    Posted by 1010
    02.Oracle/DataBase2012. 9. 20. 04:09
    반응형


    --1. Buffer Cache Hit Ratio

    SELECT ROUND(((1-(SUM(DECODE(name, 'physical reads', value,0))/

    (SUM(DECODE(name, 'db block gets', value,0))+

    (SUM(DECODE(name, 'consistent gets', value, 0))))))*100),2) || '%' "Buffer Cache Hit Ratio"

    FROM V$SYSSTAT;

    --2. Library Cache Hit Ratio

    SELECT (1-SUM (reloads)/SUM(pins))*100 "Library Cache Hit Ratio"

    From V$LIBRARYCACHE;

    --3. Data Dictionary Cache Hit Ratio

    SELECT (1-SUM(getmisses)/SUM(gets))*100 "Data Dictionary Hit Ratio"

    FROM V$ROWCACHE;

    -- 테이블 스페이스 사용량

    SELECT a.tablespace_name,

    a.total "Total(Mb)",

    a.total - b.free "Used(Mb)",

    nvl(b.free,0) "Free(Mb)",

    round((a.total - nvl(b.free,0))*100/total,0) "Used(%)"

    from ( select tablespace_name,

    round((sum(bytes)/1024/1024),0) as total

    from dba_data_files

    group by tablespace_name) a,

    ( select tablespace_name,

    round((sum(bytes)/1024/1024),0) as free

    from dba_free_space

    group by tablespace_name) b

    where a.tablespace_name = b.tablespace_name(+)

    order by a.tablespace_name;

    --오라클서버의 메모리

    select * from v$sgastat

    select pool, sum(bytes) "SIZE"

    from v$sgastat

    where pool = 'shared pool'

    group by pool

    --cpu를 많이 사용하는 쿼리문과 프로세스아이디,시리얼번호,머신 알아내기

    select c.sql_text

    ,b.SID

    , b.SERIAL#

    ,b.machine

    ,b.OSUSER

    ,b.logon_time --이 쿼리를 호출한 시간

    from v$process a, v$session b, v$sqltext c

    where a.addr = b.paddr

    and b.sql_hash_value = c.hash_value

    --and a.spid = '675958'

    order by c.PIECE

    --cpu를 많이 사용하는 쿼리문과 프로세스아이디,시리얼번호,머신 알아내기

    select c.sql_text

    from v$process a, v$session b, v$sqltext c

    where a.addr = b.paddr

    and b.sql_hash_value = c.hash_value

    and a.spid = '171'

    order by c.PIECE

    --프로세스 아이디를 이용하여 쿼리문 알아내기

    select c.sql_text

    ,b.SID

    , b.SERIAL#

    ,b.machine

    ,b.OSUSER

    ,b.logon_time --이 쿼리를 호출한 시간

    from v$process a, v$session b, v$sqltext c

    where a.addr = b.paddr

    and b.sql_hash_value = c.hash_value

    and a.spid = '1708032' --1912870/

    order by c.PIECE

    --세션 죽이기(SID,SERAIL#)

    --ALTER SYSTEM KILL SESSION '8,4093'

    --오라클 세션과 관련된 테이블*/

    --select count(*) from v$session where machine ='머신이름' and schemaname ='스키마이름'

    --현재 커서 수 확인

    SELECT sid, count(sid) cursor

    FROM V$OPEN_CURSOR

    WHERE user_name = 'ilips'

    GROUP BY sid

    ORDER BY cursor DESC

    SELECT sql_text, count(sid) cnt

    FROM v$OPEN_CURSOR

    GROUP BY sql_text

    ORDER BY cnt DESC

    select * from v$session_wait

    select sid, serial#, username, taddr, used_ublk, used_urec

    from v$transaction t, v$session s

    where t.addr = s.taddr;

    select * from sys.v_$open_cursor

    --V$LOCK 을 사용한 잠금 경합 모니터링

    SELECT s.username, s.sid, s.serial#, s.logon_time,

    DECODE(l.type, 'TM', 'TABLE LOCK',

    'TX', 'ROW LOCK',

    NULL) "LOCK LEVEL",

    o.owner, o.object_name, o.object_type

    FROM v$session s, v$lock l, dba_objects o

    WHERE s.sid = l.sid

    AND o.object_id = l.id1

    AND s.username IS NOT NULL

    --락이 걸린 세션 자세히 알아보기

    select a.sid, a.serial#,a.username,a.process,b.object_name,

    decode(c.lmode,2,'RS',3,'RX',4,'S',5,'SRX',8,'X','NO') "TABLE LOCK",

    decode (a.command,2,'INSERT',3,'SELECT',6,'UPDATE',7,'DELETE',12,'DROP TABLE',26,'LOCK TABLE','UNknown') "SQL",

    decode(a.lockwait, NULL,'NO wait','Wait') "STATUS"

    from v$session a,dba_objects b, v$lock c

    where a.sid=c.sid and b.object_id=c.id1

    and c.type='TM'

    --락이 걸린 세션 간단히 알아보기

    select a.sid, a.serial#, b.type, c.object_name, a.program, a.lockwait,

    a.logon_time, a.process, a.osuser, a.terminal

    from v$session a, v$lock b, dba_objects c

    where a.sid = b.sid

    and b.id1 = c.object_id

    and b.type = 'TM';

    select a.sid, a.serial#, a.username, a.process, b.object_name

    from v$session a , dba_objects b, v$lock c

    where a.sid=c.sid and b.object_id = c.id1

    and c.type = 'TM'

    --락이 걸린 세션을 찾아 내어 세션을 죽이려고 해도 죽지 않는 경우

    --아래 쿼리문으로 OS단의 PROCESS ID를 찾아내어 OS에서 죽인다

    --kill -9 프로세스아이디

    select substr(s.username,1,11) "ORACLE USER", p.pid "PROCESS ID",

    s.sid "SESSION ID", s.serial#, osuser "OS USER",

    p.spid "PROC SPID",s.process "SESS SPID", s.lockwait "LOCK WAIT"

    from v$process p, v$session s, v$access a

    where a.sid=s.sid and

    p.addr=s.paddr and

    s.username != 'SYS'

    --위 쿼리문의 결과가 있다면 락이 걸린 세션이 있다는것이므로 아래의 쿼리문으로 세션을 죽인다

    ALTER SYSTEM KILL SESSION '11,39061'

    alter session으로 죽지않는 프로세스 죽이기

    1.oracle이 설치된 서버에 텔넷으로 root로 접속한다

    2.su -오라클계정

    3.sqlplus '/as sysdba''

    4.connect system/sys

    5.ALTER SYSTEM KILL SESSION '137,1723'

    출처 : 파란 블로그 DINO 님.

    Posted by 1010
    02.Oracle/DataBase2012. 9. 18. 05:21
    반응형
    instr(object_name,'abcd')>0

    where 절 밑에 object_name like '%abcd%' 로 검색하는 것보다

    instr(object_name,'abcd') > 0 로 검색하면 더 빠르다..

     

     

     

    Full Text Indexing using Oracle Text

    Oracle Text, previously know as interMedia Text and ConText, is an extensive full text indexing technology allowing you to efficiently query free text and produce document classification applications. In this article I'll only scratch the surface of this very complex feature.

    The examples in this article require access to the CTX_DDL package, which is granted as follows.

    GRANT EXECUTE ON CTX_DDL TO <username>;

    CONTEXT Indexes

    The CONTEXT index type is used to index large amounts of text such as Word, PDF, XML, HTML or plain text documents. In this example we will store the data in a BLOB column, which allows us to store binary documents like Word and PDF as well as plain text. Using a CLOB is preferable if only plain text documents are being used.

    First we build a sample schema to hold our data.

    DROP TABLE my_docs;
    DROP SEQUENCE my_docs_seq;
    DROP PROCEDURE load_file_to_my_docs;
    
    CREATE TABLE my_docs (
      id    NUMBER(10)     NOT NULL,
      name  VARCHAR2(200)  NOT NULL,
      doc   BLOB           NOT NULL
    );
    
    ALTER TABLE my_docs ADD (
      CONSTRAINT my_docs_pk PRIMARY KEY (id)
    );
    
    CREATE SEQUENCE my_docs_seq;
    
    CREATE OR REPLACE DIRECTORY documents AS 'C:\work';

    Next we load several files as follows.

    CREATE OR REPLACE PROCEDURE load_file_to_my_docs (p_file_name  IN  my_docs.name%TYPE) AS
      v_bfile      BFILE;
      v_blob       BLOB;
    BEGIN
      INSERT INTO my_docs (id, name, doc)
      VALUES (my_docs_seq.NEXTVAL, p_file_name, empty_blob())
      RETURN doc INTO v_blob;
    
      v_bfile := BFILENAME('DOCUMENTS', p_file_name);
      Dbms_Lob.Fileopen(v_bfile, Dbms_Lob.File_Readonly);
      Dbms_Lob.Loadfromfile(v_blob, v_bfile, Dbms_Lob.Getlength(v_bfile));
      Dbms_Lob.Fileclose(v_bfile);
    
      COMMIT;
    END;
    /
    
    EXEC load_file_to_my_docs('FullTextIndexingUsingOracleText9i.doc');
    EXEC load_file_to_my_docs('FullTextIndexingUsingOracleText9i.asp');
    EXEC load_file_to_my_docs('XMLOverHTTP9i.asp');
    EXEC load_file_to_my_docs('UNIXForDBAs.asp');
    EXEC load_file_to_my_docs('emp_ws_access.sql');
    EXEC load_file_to_my_docs('emp_ws_test.html');
    EXEC load_file_to_my_docs('9ivsSS2000forPerformanceV22.pdf');

    Next we create a CONTEXT type index on the doc column and gather table statistics.

    CREATE INDEX my_docs_doc_idx ON my_docs(doc) INDEXTYPE IS CTXSYS.CONTEXT;
    
    EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_DOCS', cascade=>TRUE);

    Finally we query table looking for documents with specific content.

    SELECT SCORE(1) score, id, name
    FROM   my_docs
    WHERE  CONTAINS(doc, 'SQL Server', 1) > 0
    ORDER BY SCORE(1) DESC;
    
         SCORE         ID NAME
    ---------- ---------- ------------------------------------------------
           100        127 9ivsSS2000forPerformanceV22.pdf
    
    1 row selected.
    
    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=2 Bytes=58)
       1    0   SORT (ORDER BY) (Cost=4 Card=2 Bytes=58)
       2    1     TABLE ACCESS (BY INDEX ROWID) OF 'MY_DOCS' (Cost=2 Card=2 Bytes=58)
       3    2       DOMAIN INDEX OF 'MY_DOCS_DOC_IDX' (Cost=0)
    
    
    SELECT SCORE(1) score, id, name
    FROM   my_docs
    WHERE  CONTAINS(doc, 'XML', 1) > 0
    ORDER BY SCORE(1) DESC;
    
         SCORE         ID NAME
    ---------- ---------- ------------------------------------------------
            74        123 XMLOverHTTP9i.asp
             9        125 emp_ws_access.sql
    
    2 rows selected.
    
    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=2 Bytes=58)
       1    0   SORT (ORDER BY) (Cost=4 Card=2 Bytes=58)
       2    1     TABLE ACCESS (BY INDEX ROWID) OF 'MY_DOCS' (Cost=2 Card=2 Bytes=58)
       3    2       DOMAIN INDEX OF 'MY_DOCS_DOC_IDX' (Cost=0)

    CTXCAT Indexes

    The CTXCAT index type is best suited to smaller text fragments that must be indexed along with other relational data. In this example the data will be stored in a VARCHAR2 column.

    First we create a schema to hold the data.

    DROP TABLE my_items;
    DROP SEQUENCE my_items_seq;
    EXEC CTX_DDL.DROP_INDEX_SET('my_items_iset');
    
    CREATE TABLE my_items (
      id           NUMBER(10)      NOT NULL,
      name         VARCHAR2(200)   NOT NULL,
      description  VARCHAR2(4000)  NOT NULL,
      price        NUMBER(7,2)     NOT NULL
    );
    
    ALTER TABLE my_items ADD (
      CONSTRAINT my_items_pk PRIMARY KEY (id)
    );
    
    CREATE SEQUENCE my_items_seq;

    Next we populate the schema with some dummy data.

    BEGIN
      FOR i IN 1 .. 1000 LOOP
        INSERT INTO my_items (id, name, description, price)
        VALUES (my_items_seq.NEXTVAL, 'Bike: '||i, 'Bike Description ('||i||')', i);
      END LOOP;
    
      FOR i IN 1 .. 1000 LOOP
        INSERT INTO my_items (id, name, description, price)
        VALUES (my_items_seq.NEXTVAL, 'Car: '||i, 'Car Description ('||i||')', i);
      END LOOP;
    
      FOR i IN 1 .. 1000 LOOP
        INSERT INTO my_items (id, name, description, price)
        VALUES (my_items_seq.NEXTVAL, 'House: '||i, 'House Description ('||i||')', i);
      END LOOP;
    
      COMMIT;
    END;
    /

    Next we create a CTXCAT index on the DESCRIPTION and PRICE columns and gather table statistics. In order to create the index we must create an index-set with a sub-index for each column referenced by the CATSEARCH function.

    EXEC CTX_DDL.CREATE_INDEX_SET('my_items_iset');
    EXEC CTX_DDL.ADD_INDEX('my_items_iset','price');
    
    CREATE INDEX my_items_name_idx ON my_items(description) INDEXTYPE IS CTXSYS.CTXCAT
    PARAMETERS ('index set my_items_iset');
    
    EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_ITEMS', cascade=>TRUE);

    Finally we query table looking for items with a description that contains our specified words and an appropriate price.

    SELECT id, price, name
    FROM   my_items
    WHERE  CATSEARCH(description, 'Bike', 'price BETWEEN 1 AND 5')> 0;
    
            ID      PRICE NAME
    ---------- ---------- ------------------------------------------------
             1          1 Bike: 1
             2          2 Bike: 2
             3          3 Bike: 3
             4          4 Bike: 4
             5          5 Bike: 5
    
    5 rows selected.
    
    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=150 Bytes=6000)
       1    0   TABLE ACCESS (BY INDEX ROWID) OF 'MY_ITEMS' (Cost=2 Card=150 Bytes=6000)
       2    1     DOMAIN INDEX OF 'MY_ITEMS_NAME_IDX'
    
    
    SELECT id, price, name
    FROM   my_items
    WHERE  CATSEARCH(description, 'Car', 'price BETWEEN 101 AND 105 ORDER BY price DESC')> 0;
    
            ID      PRICE NAME
    ---------- ---------- ------------------------------------------------
          1105        105 Car: 105
          1104        104 Car: 104
          1103        103 Car: 103
          1102        102 Car: 102
          1101        101 Car: 101
    
    5 rows selected.
    
    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=150 Bytes=6000)
       1    0   TABLE ACCESS (BY INDEX ROWID) OF 'MY_ITEMS' (Cost=2 Card=150 Bytes=6000)
       2    1     DOMAIN INDEX OF 'MY_ITEMS_NAME_IDX'

    Every column used to restrict the selection or order the output in the CATSEARCH function should have a sub-index within the index-set. The CTXCAT index type is transactional so there is no need to synchronize the index.

    CTXRULE Indexes

    The CTXRULE index type can be used to build document classification applications.

    First we must define our document categories and store them, along with a suitable query for the MATCHES function.

    DROP TABLE my_doc_categories;
    DROP TABLE my_categories;
    DROP SEQUENCE my_categories_seq;
    DROP TABLE my_docs;
    DROP SEQUENCE my_docs_seq;
    
    CREATE TABLE my_categories (
      id        NUMBER(10)      NOT NULL,
      category  VARCHAR2(30)    NOT NULL,
      query     VARCHAR2(2000)  NOT NULL
    );
    
    ALTER TABLE my_categories ADD (
      CONSTRAINT my_categories_pk PRIMARY KEY (id)
    );
    
    CREATE SEQUENCE my_categories_seq;
    
    INSERT INTO my_categories VALUES(my_categories_seq.NEXTVAL, 'Oracle', 'ABOUT(Oracle)');
    INSERT INTO my_categories VALUES(my_categories_seq.NEXTVAL, 'SQL Server', 'ABOUT(SQL Server)');
    INSERT INTO my_categories VALUES(my_categories_seq.NEXTVAL, 'UNIX', 'ABOUT(UNIX)');

    Next we create a table to hold our documents.

    CREATE TABLE my_docs (
      id    NUMBER(10)     NOT NULL,
      name  VARCHAR2(200)  NOT NULL,
      doc   CLOB           NOT NULL
    );
    
    ALTER TABLE my_docs ADD (
      CONSTRAINT my_docs_pk PRIMARY KEY (id)
    );
    
    CREATE SEQUENCE my_docs_seq;

    Then we create an intersection table to resolve the many-to-many relationship between documents and categories.

    CREATE TABLE my_doc_categories (
      my_doc_id       NUMBER(10)  NOT NULL,
      my_category_id  NUMBER(10)  NOT NULL
    );
    
    ALTER TABLE my_doc_categories ADD (
      CONSTRAINT my_doc_categories_pk PRIMARY KEY (my_doc_id, my_category_id)
    );

    Next we create a BEFORE INSERT trigger on the MY_DOCS table to automatically assign the documents to the relevant categories as they are being inserted. The MATCHES function is used to decide if the document matches any of our gategory queries. The resulting cursor is used to insert the matches into the intersect table.

    CREATE OR REPLACE TRIGGER my_docs_trg
      BEFORE INSERT ON my_docs
      FOR EACH ROW
    BEGIN
      FOR c1 IN (SELECT id
                 FROM   my_categories
                 WHERE  MATCHES(query, :new.doc)>0)
      LOOP
        BEGIN
          INSERT INTO my_doc_categories(my_doc_id, my_category_id)
          VALUES (:new.id, c1.id);
        EXCEPTION
          WHEN OTHERS THEN
            NULL;
        END;
      END LOOP;
    END;
    /

    Next we create the CTXRULE index to support the trigger. For completeness we also create a CONTEXT index on the document itself, although this is not involved in the category assignment process.

    CREATE INDEX my_categories_query_idx ON my_categories(query) INDEXTYPE IS CTXSYS.CTXRULE;
    CREATE INDEX my_docs_doc_idx ON my_docs(doc) INDEXTYPE IS CTXSYS.CONTEXT;
    
    EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_CATEGORIES', cascade=>TRUE);
    EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_DOCS', cascade=>TRUE);
    EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_DOC_CATEGORIES', cascade=>TRUE);

    Finally we test the mechanism by inserting some rows and checking the classification.

    INSERT INTO my_docs (id, name, doc)
    VALUES (my_docs_seq.NEXTVAL, 'Oracle Document', 'This document constains the word Oracle!');
    
    INSERT INTO my_docs (id, name, doc)
    VALUES (my_docs_seq.NEXTVAL, 'SQL Server Document', 'This document constains the words SQL Server!');
    
    INSERT INTO my_docs (id, name, doc)
    VALUES (my_docs_seq.NEXTVAL, 'UNIX Document', 'This document constains the word UNIX!');
    
    INSERT INTO my_docs (id, name, doc)
    VALUES (my_docs_seq.NEXTVAL, 'Oracle UNIX Document', 'This document constains the word UNIX and the word Oracle!');
    
    
    COLUMN name FORMAT A30;
    SELECT a.name, b.category
    FROM   my_docs a,
           my_categories b,
           my_doc_categories c
    WHERE  c.my_doc_id      = a.id
    AND    c.my_category_id = b.id;
    
    NAME                           CATEGORY
    ------------------------------ ------------------------------
    Oracle Document                Oracle
    SQL Server Document            SQL Server
    UNIX Document                  UNIX
    Oracle UNIX Document           UNIX
    Oracle UNIX Document           Oracle
    
    5 rows selected.

    The output shows that the documents have been assigned to the correct categories. Note, the "Oracle UNIX Document" document has been assigned to both the "Oracle" and "UNIX" categories.

    Index Maintenance

    Not all Oracle Text indexes are automatically updated as records are added or deleted. To synchronize the index with the table you must call.

    SQL> EXEC CTX_DDL.SYNC_INDEX('my_docs_doc_idx');

    Regular synchronizations of the index can be automated using the DBMS_JOB package. The following script is provided to make this task easier.

    $ORACLE_HOME/ctx/sample/script/drjobdml.sql

    It can be called from SQL*Plus whilst logged on as the index owner as follows.

    SQL> @drjobdml.sql index-name interval-mins
    SQL> @drjobdml.sql my_docs_doc_idx 60

    Regular synchronization of text indexes can cause fragmentation which affects query performance. To correct this situation the index can be rebuilt or optimized. Index optimization can be performed in three basic modes (FAST, FULL or TOKEN). The FAST mode compacts fragmented rows but does not remove old data.

    BEGIN
      CTX_DDL.OPTIMIZE_INDEX('my_docs_doc_idx','FAST');
    END;
    /

    The FULL mode optimizes either the entire index or a portion of it, with old data removed.

    BEGIN
      CTX_DDL.OPTIMIZE_INDEX('my_docs_doc_idx','FULL');
    END;
    /

    The TOKEN mode perfoms a full optimization for a specific token.

    BEGIN
      CTX_DDL.OPTIMIZE_INDEX('my_docs_doc_idx','TOKEN', token=>'Oracle');
    END;
    /

    For more information see:

    Hope this helps. Regards Tim...

    Back to the Top.

    Posted by 1010
    02.Oracle/DataBase2012. 9. 18. 03:26
    반응형

    출처 : http://ryu1hwan.tistory.com/27

    머릿말

    오늘은 CASE문에 강의해보도록 하겠습니다.
    이전에 DECODE에 대해서 설명드렸는데, DECODE와 동일한 기능을 하는 문장입니다.
    DECODE보다 이해하기 쉽고, 사용하기 편한 방법입니다. 단, DECODE보다 코딩양은 약간 길어집니다.
    그러므로, 만드는 SQL에서 필요한 상황에 따라 DECODE와 CASE를 섞어 쓰시는게 현명하다고 생각됩니다.


    Lesson 7: CASE문

    작성방법
    SELECT T1.STORE_ID
    ,T1.STORE_ADDR
    ,T1.REGION_CD
    ,T2.REGION_GD
    ,CASE WHEN T2.REGION_GD = 'S' THEN 'High Grade'
    WHEN T2.REGION_GD = 'A' THEN 'High Grade'
    ELSE 'Low Grade' END NEW_REGION_GD
    FROM SQL_TEST.MA_STORE T1
    ,SQL_TEST.CD_REGION T2
    WHERE T2.REGION_CD = T1.REGION_CD
    - STORE의 지역에 대한 등급을 High Grade와 Low Grade로 변경해서 출력한다.
    - CASE WHEN을 기술 한 후 조건을 기술하고, THEN 다음에 조건을 만족할 경우의 결과를 적는다.
    - CASE WHEN조건을 만족하지 않을 경우 처리한 내용을 ELSE절에 적는다.


    중첩된 CASE문
    - 여러개의 CASE문을 중첩해서 사용할 수 있다.
    SELECT T1.STORE_ID
    ,T1.STORE_ADDR
    ,CASE WHEN T1.STORE_SIZE >= 100 THEN
    CASE WHEN T2.REGION_GD IN ('S') THEN 'High grade'
    WHEN T2.REGION_GD IN ('A','B') THEN 'Mid Grade'
    ELSE 'Low Grade'
    END
    WHEN T1.STORE_SIZE >= 50 THEN
    CASE WHEN T2.REGION_GD IN ('S', 'A') THEN 'High Grade'
    WHEN T2.REGION_GD IN ('B') THEN 'Mid Grade'
    ELSE 'Low Grade'
    END
    ELSE
    CASE WHEN T2.REGION_GD IN ('S', 'A', 'B') THEN 'High Grade'
    ELSE 'Low Grade'
    END
    END STORE_SIZE_GD
    FROM SQL_TEST.MA_STORE T1
    ,SQL_TEST.CD_REGION T2
    WHERE T1.REGION_CD = T2.REGION_CD
    - STORE의 SIZE에 대한 등급을 STORE의 지역(REGION), STORE_SIZE별로 STORE SIZE등급을 구한다.
    - 이러한 중첩된 CASE는 사용자가 원하는 다양한 결과를 얻어 내는데 큰 도움이 된다.

    GROUP BY와 CASE/DECODE : GROUP BY컬럼에 CASE/DECODE사용
    - GROUP BY를 정의하는 컬럼에 CASE나 DECODE를 사용하는 방법이다.
    SELECT CASE WHEN T1.REGION_GD = 'S' THEN 'High Grade'
    WHEN T1.REGION_GD = 'A' THEN 'High Grade'
    ELSE 'Low Grade' END GRADE
    ,COUNT(*) REGION_CNT
    FROM SQL_TEST.CD_REGION T1
    GROUP BY CASE WHEN T1.REGION_GD = 'S' THEN 'High Grade'
    WHEN T1.REGION_GD = 'A' THEN 'High Grade'
    ELSE 'Low Grade' END;

    SELECT
    DECODE(T1.REGION_GD,'S','High Grade','A','High Grade','Low Grade') GRADE
    ,COUNT(*) REGION_CNT
    FROM SQL_TEST.CD_REGION T1
    GROUP BY DECODE(T1.REGION_GD,'S','High Grade','A','High Grade','Low Grade');
    - CASE/DECODE를 통해 REGION_GD가 S나 A인 경우는 High Grade로 표시하고, 나머지는 Low Grade로 집계해서 Region(지역)별로 카운트를 한다.
    - 주의 할 점은 GROUP BY에 명시한 내용 그대로 SELECT절에 적어야 한다는 것이다.
    - 추가로 예제를 하나 더 보도록 하자.
    SELECT CASE WHEN T1.STORE_SIZE >= 100 AND T2.REGION_GD IN ('S') THEN 'High'
    WHEN T1.STORE_SIZE >= 100 AND T2.REGION_GD IN ('A','B','C') THEN 'Normal'
    WHEN T1.STORE_SIZE < 100 AND T2.REGION_GD IN ('S','A') THEN 'High'
    WHEN T1.STORE_SIZE < 100 AND T2.REGION_GD IN ('B','C') THEN 'Low'
    END STORE_SIZE_GD
    ,COUNT(*) STORE_CNT
    FROM SQL_TEST.MA_STORE T1
    ,SQL_TEST.CD_REGION T2
    WHERE T1.REGION_CD = T2.REGION_CD
    GROUP BY CASE WHEN T1.STORE_SIZE >= 100 AND T2.REGION_GD IN ('S') THEN 'High'
    WHEN T1.STORE_SIZE >= 100 AND T2.REGION_GD IN ('A','B','C') THEN 'Normal'
    WHEN T1.STORE_SIZE < 100 AND T2.REGION_GD IN ('S','A') THEN 'High'
    WHEN T1.STORE_SIZE < 100 AND T2.REGION_GD IN ('B','C') THEN 'Low'
    END


    GROUP BY + CASE/DECODE : 집계함수 내에 CASE/DECODE사용
    - GROUP BY를 사용해서 값을 집계 할 때, CASE/DECODE를 사용해서, 특정 경우에만 값이 집계되도록 하는 방법이다.
    SELECT T1.STORE_ID
    ,SUM(CASE WHEN TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D')='1' THEN 1 END) SUN_ORD
    ,SUM(CASE WHEN TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D')='2' THEN 1 END) MON_ORD
    ,SUM(CASE WHEN TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D')='3' THEN 1 END) TUE_ORD
    ,SUM(CASE WHEN TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D')='4' THEN 1 END) WED_ORD
    ,SUM(CASE WHEN TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D')='5' THEN 1 END) THU_ORD
    ,SUM(CASE WHEN TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D')='6' THEN 1 END) FRI_ORD
    ,SUM(CASE WHEN TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D')='7' THEN 1 END) SAT_ORD
    FROM SQL_TEST.HI_ORDER T1
    WHERE T1.ORDER_YMD LIKE '200901%'
    GROUP BY T1.STORE_ID
    ORDER BY T1.STORE_ID;


    SELECT
    T1.STORE_ID
    ,SUM(DECODE(TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D'),'1',1,0)) SUN_ORD
    ,SUM(DECODE(TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D'),'2',1,0)) MON_ORD
    ,SUM(DECODE(TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D'),'3',1,0)) TUE_ORD
    ,SUM(DECODE(TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D'),'4',1,0)) WED_ORD
    ,SUM(DECODE(TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D'),'5',1,0)) THU_ORD
    ,SUM(DECODE(TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D'),'6',1,0)) FRI_ORD
    ,SUM(DECODE(TO_CHAR(TO_DATE(T1.ORDER_YMD,'YYYYMMDD'),'D'),'7',1,0)) SAT_ORD
    FROM SQL_TEST.HI_ORDER T1
    WHERE T1.ORDER_YMD LIKE '200901%'
    GROUP BY T1.STORE_ID
    ORDER BY T1.STORE_ID;

    - 데이터를 STORE_ID별로 GROUP BY한다.
    - CASE/DECODE를 사용해서 ORDER_DT의 요일값(1(일요일)~7(토요일))에 따라 주문 건수를 집계
    - 예를 들어 ORDER_DT가 요일 값ㅣ 1(일요일)일 때는 숫자 1을 아닌 경우는 0으로 변경해서 합계를 구해 SUN_ORD를 만든다.
    - 이와 같은 유형은 데이터 분석 화면을 만들때 많이 사용되는 유형입니다.
    - 추가로 하나의 예제를 보도록 하자.
    SELECT T1.ORDER_YMD
    ,SUM(CASE WHEN T2.PROD_SIZE_CD = 'L' THEN 1 END) LARGE_ORD_CNT
    ,SUM(CASE WHEN T2.PROD_SIZE_CD = 'L' THEN T2.ITEM_SALE_PRC END) LARGE_ORD_AMT
    ,SUM(CASE WHEN T2.PROD_SIZE_CD = 'M' THEN 1 END) MIDDLE_ORD_CNT
    ,SUM(CASE WHEN T2.PROD_SIZE_CD = 'M' THEN T2.ITEM_SALE_PRC END) MIDDLE_ORD_AMT
    ,SUM(CASE WHEN T2.PROD_SIZE_CD = 'S' THEN 1 END) SMALL_ORD_CNT
    ,SUM(CASE WHEN T2.PROD_SIZE_CD = 'S' THEN T2.ITEM_SALE_PRC END) SMALL_ORD_AMT
    FROM SQL_TEST.HI_ORDER T1
    ,SQL_TEST.HI_ORDER_ITEM T2
    WHERE T1.ORDER_YMD LIKE '200901%'
    AND T1.ORDER_NO = T2.ORDER_NO
    GROUP BY T1.ORDER_YMD
    ORDER BY T1.ORDER_YMD
    - 주문일자별로 데이터를 GROUP BY한다.
    - 주문된 제품의 SIZE별로 주문 건수를 카운트 한다.

    GROUP BY + CASE/DECODE : 집계 함수 바깥에 CASE/DECODE를 사용
    - GROUP BY와 집계함수를 먼저 사용하고, 집계 함수의 결과에 대해 CASE/DECODE를 사용한다.
    SELECT T1.STORE_ID
    ,SUM(T2.ORDER_AMT) ORDER_AMT
    ,CASE WHEN SUM(T2.ORDER_AMT) > 15 THEN 'High'
    WHEN SUM(T2.ORDER_AMT) > 10 THEN 'Mid'
    WHEN SUM(T2.ORDER_AMT) > 0 THEN 'Low'
    ELSE 'Bad' END SALE_GD
    FROM SQL_TEST.MA_STORE T1
    ,SQL_TEST.HI_ORDER T2
    WHERE T2.STORE_ID = T1.STORE_ID(+)
    AND T2.ORDER_YMD(+) = '20090101'
    GROUP BY T1.STORE_ID
    - STORE별 판매 금액 합계에 따라 판매 등급을 구한다.
    - 집계뙨 금액 합계에 따라 15보다 크면 High, 10~15사이이면 Mid, 나머지는 Low로 판매등급을 구한다.
    SELECT T3.REGION_GD
    ,T1.STORE_ID
    ,SUM(T2.ORDER_AMT) ORDER_AMT
    ,CASE WHEN T3.REGION_GD = 'S' THEN
    CASE WHEN SUM(T2.ORDER_AMT) > 15 THEN 'High'
    WHEN SUM(T2.ORDER_AMT) > 10 THEN 'Mid'
    ELSE 'Low' END
    WHEN T3.REGION_GD = 'A' THEN
    CASE WHEN SUM(T2.ORDER_AMT) > 12 THEN 'High'
    WHEN SUM(T2.ORDER_AMT) > 9 THEN 'Mid'
    ELSE 'Low' END
    WHEN T3.REGION_GD = 'B' THEN
    CASE WHEN SUM(T2.ORDER_AMT) > 9 THEN 'High'
    WHEN SUM(T2.ORDER_AMT) > 5 THEN 'Mid'
    ELSE 'Low' END
    WHEN T3.REGION_GD = 'C' THEN
    CASE WHEN SUM(T2.ORDER_AMT) > 5 THEN 'High'
    WHEN SUM(T2.ORDER_AMT) > 3 THEN 'Mid'
    ELSE 'Low' END
    END SALE_GD
    FROM SQL_TEST.MA_STORE T1
    ,SQL_TEST.HI_ORDER T2
    ,SQL_TEST.CD_REGION T3
    WHERE T2.STORE_ID = T1.STORE_ID(+)
    AND T2.ORDER_YMD(+) = '20090101'
    AND T3.REGION_CD = T1.REGION_CD
    GROUP BY T3.REGION_GD, T1.STORE_ID
    - 중첩된 CASE를 이용해서 REGION_GD에 따라 판매등급의 판매금액 기준을 다르게 적용


    원래 컬럼을 변경하는 CASE, DECODE문이 자주 나오는 것은 성능상 문제가 있을 수 있다.
    - 수작업으로 작성하는 1회성 리포트라면 크게 문제 없다.
    - CASE, DECODE를 사용하지 않고 SQL을 작성할 수 있는 가를 고려한다.
    - CASE, DECODE를 제거할 수 있도록 테이블을 변경 할 수 있는 가를 고려한다.
    - 위의 상황이 불가피 할 경우 상급개발자나 DBAㅘ 함께 성능점검을 하도록 한다.

    CASE를 사용할 것인가? DECODE를 사용할 것인가?
    - 기본적인 구문은 DECODE가 간단하지만, 복잡한 조건에서는 표현하기가 어렵다.
    - DECODE를 이용해 간단히 표현이 가능하면 DECODE를,
    - 조건식이 너무 복잡할 경우는 CASE를 사용하도록 하자.


    맺음말.
    오늘도 저의 지루한 글 읽으시느라고 고생들 하셨습니다.^^
    누가 보실지는 모르겠지만, 아무튼, 지금까지 따라 오셨다면,
    이젠 SQL문법 자체는 거의 모두 익히신거네요.^^
    Posted by 1010
    02.Oracle/DataBase2012. 9. 2. 08:01
    반응형

    출처 : http://scidb.tistory.com/entry/SQL-Tuning-Advisor

    SQL Tuning Advisor

    개념 :
    - Oracle 10g 의 새기능인 Automatic Workload Repository(AWR) 의 튜닝 솔루션인
    Server-Based Advisors의 콤포넌트 중의 하나이다.
    - Server-Based Advisors 는 다음과 같이구성된다.
    Automatic Database Diagnostic Monitor (ADDM)
    SQL Tuning Advisor
    SQL Access Advisor
    Shared Pool Advisor
    MTTR Advisor
    - SQL Tuning Advisor 는 SQL 구문들을 분석하여 튜닝방법을 제안한다.
    - 튜닝방법을 제안하는 것에서 그치지 않고 쉽게적용할수 있도록 도와주기도 한다.


    사용방법:
    -엔터프라이져 매니져를 통해 관리할수도 있지만 여기서는 DBMS_SQLTUNE 패키지를
    사용하기로 한다.

    1) 튜닝 Task 생성

    Declare
    ret_val varchar2(2000);
    Begin
    ret_val:=dbms_sqltune.create_tuning_task(sql_text=>
    'SELECT *FROM EMP WHERE EMPNO=7934');

    dbms_output.put_line('Output: 'ret_val);

    end;/

    OUTPUT: TASK_00004

    2) 튜닝가이드 생성

    Exec Dbms_sqltune.execute_tuning_task('TASK_00004');


    3) 튜닝 리포트 생성
    set serveroutput on size 999999
    set long 999999
    select dbms_sqltune.report_tuning_task ('TASK_00004') from dual;

    4)결과분석

    GENERAL INFORMATION SECTION
    ----------------------------------------------------
    Tuning Task Name : TASK_00004
    Scope : COMPREHENSIVE
    Time Limit(seconds): 1800
    Completion Status : COMPLETED
    Started at : 11/06/2003 01:47:38

    사용자 삽입 이미지

    -------------------------------------------------------------------------------------------

    위 결과를 보면 recommendation 항목에서 튜닝제안을 했으며 구체적인 스크립트까지
    제시하고 있다.
    Rationale 항목에서는 튜닝방법대로 했을경우 옵티마이져가 어떻게 반응하는지를 보여준다.
    위의 예제에서는 통계정보를 생성하면 실행계획이 바뀐다는점을 알려주고 있다.
    신기하지 않은가?

     

    Posted by 1010