sql 2009. 12. 16. 15:08

oracle ] rank() function


/*
순위와 관련된 오라클 함수

RANK()
ROW_NUMBER()
DENSE_RANK()

RANK 함수가 동일한 값에 대해서는 동일한 순위를 부여하는데 반해,
ROW_NUMBER 함수는 유니크한 순위를 부여한다.
DENSE_RANK()함수는 중복 RANK의 수와 무관하게 순위를 부여한다.

oracle 8i(8.1.6)이상
plsql내에서는 oracle 9i이상
*/

-- RANK() 함수를 사용하여 급여순으로 순위 부여
SELECT empno, ename, sal, RANK () OVER (ORDER BY sal DESC) AS RANK
FROM   emp


-- PARTITION BY를 이용해서 그룹별로 순위 부여
SELECT deptno, ename, sal,
       RANK () OVER (PARTITION BY deptno ORDER BY sal DESC) AS RANK
FROM   emp


-- 중복값과 무관하게 순위부여
SELECT empno, ename, sal, DENSE_RANK () OVER (ORDER BY sal DESC) AS RANK
FROM   emp


-- ROW_NUMBER()를 사용하여 유니크한 순서부여, 순위를 자세히 조정하고 싶으면 ORDER BY절 수정
SELECT empno, ename, sal,
       ROW_NUMBER () OVER (ORDER BY sal DESC, ename) AS ROW_NUMBER
FROM   emp


-- 다음 두 쿼리는 같은 결과를 반환, 직업별로 가장높은 월급을 받는사람
SELECT   a.ename, a.job, a.sal
FROM     emp a,
         (SELECT   MAX (sal) max_sal, job
          FROM     emp
          GROUP BY job) b
WHERE    a.sal = b.max_sal AND a.job = b.job
ORDER BY a.ename


SELECT   ename, job, sal
FROM     (SELECT ename, job, sal,
                 RANK () OVER (PARTITION BY job ORDER BY sal DESC) AS RANK
          FROM   emp)
WHERE    RANK = 1
ORDER BY ename

sql 2009. 10. 29. 13:03

oracle - merge 문법

sql 2009. 10. 8. 19:51

ROWID 및 ROW_NUMBER()를 이용해서 테이블내에 중복된 테이타 찾기


중복된 테이타 찾기

SELECT * FROM MENU a
WHERE rowid > ANY (SELECT rowid FROM MENU b
                               WHERE b.PROGRAM=a.PROGRAM)
ORDER BY A.PROGRAM 




SELECT
*
FROM(
SELECT A,B,C,D,ROW_NUMBER() OVER(PARTITION BY C ORDER BY A) CNT FROM TEMPDATA
)
WHERE CNT = 1
sql 2009. 7. 1. 17:15

ORALCE] CURSOR 커서 사용하기


여러개의 로우를 하나의 컬럼으로 만들어서 반환하는 FUNCTION이다

아래 간략한 설명은 
SELECT 절에서 특정테이블 컬럼을 값을 인자로 넘겨서 해당인자를 조건으로하여
검색된 여러개의 로우를 LOOF를 돌면서 하나의 컬럼으로 만들고 해당 값을 반환하는 형식이다.

CREATE OR REPLACE FUNCTION   ABEEK2009.GET_SUSTNAME
    (IN_PROGRAM  IN VARCHAR2)   
    RETURN VARCHAR2
    IS
    NAMESTR VARCHAR2(1000);
   
    V_HAKKWACD ABK_PROGRAMGROUP.HAKKWACD%type;
    V_HAKKWANM ABK_PROGRAMGROUP.HAKKWANM%type;        

       CURSOR C1 IS
         SELECT HAKKWACD, HAKKWANM FROM ABK_PROGRAMGROUP
             WHERE PROGRAM = IN_PROGRAM
   ORDER BY HAKKWACD;   
    BEGIN    
  NAMESTR := ''; 
       
        OPEN C1;           
         LOOP    
   FETCH C1 INTO  V_HAKKWACD, V_HAKKWANM;
          
          EXIT WHEN C1%NOTFOUND;
                 IF NAMESTR IS NULL THEN
                    NAMESTR :=  V_HAKKWACD || ' ' || V_HAKKWANM ;    
                 ELSE
                   NAMESTR := NAMESTR  || ',' || V_HAKKWACD || ' ' || V_HAKKWANM;
                 END IF;
         END LOOP;    
         CLOSE C1;        
       
       RETURN NAMESTR;    
      
   END;



아래와 같은 결과를 얻을수있다.

   

sql 2009. 5. 19. 16:14

START WITH CONNECT BY~


◈ START WITH
 
 - 계층 질의의 루트(부모행)로 사용될 행을 지정 합니다..
 - 서브쿼리를 사용할 수도 있습니다.
 
 
◈ CONNECT BY
 
 - 이 절을 이용하여 계층 질의에서 상위계층(부모행)과 하위계층(자식행)의 관계를 규정 합니다.
 - 보통 PRIOR 연산자를 많이 사용 합니다..
 - 서브쿼리를 사용할 수 없습니다..
 - PRIOR
   PRIOR 이 붙는 column 이 가져온 row 의 column을 의미한다. 즉 상위에 존재할 데이타가 되

   게 된다. 어느쪽에 붙느냐 잘 따져 본다.
  
 
◈ CONNECT BY의 실행순서는 다음과 같습니다.
 
 - 첫째 START WITH절
 - 둘째 CONNECT BY 절
 - 세째 WHERE 절 순서로 풀리게 되어있습니다.

 

◈ SYNTEX
  
   SELECT
   FROM
   START WITH
   CONNECT BY PRIOR
   AND
   ORDER SIBLINGS BY

 

   or

  

   SELECT
   FROM
   WHERE
   START WITH
   CONNECT BY PRIOR
   ORDER SIBLINGS BY
  
◈ 이용
   1) 쇼핑목 카테고리 관계 - 대분류, 중분류, 소분류 등을 트리 구조로
   2) 게시판 에서 일반글 과 답글과의 관계 등을 트리 구조로    
  
◈ 데이터가 많아질 경우....
 
 - 첫째로 풀리는 START WITH job='PRESIDENT' job 컬럼에 index가 생성되어 있지 않는다면
    속도를 보장할 수 없습니다.
 
 - 그리고 둘째로 풀리는 CONNECT BY PRIOR empno = mgr 역시 PRIOR 쪽의 컬럼값이 상수가
   되기 때문에 MGR컬럼에 index를 생성하여야 CONNECT BY의 속도를 보장할 수 있습니다.
  
 - 계층구조를 CONNECT BY, START WITH로 풀면 부분범위 처리가 불가능하고 Desc으로
   표현하기가 어렵 습니다.
  

--------------- 설명
-- 아래 예제 1
1) job 이 president 인 row 을 가져온다.
2) 가져온 row 에서 prior 이 붙은 comumn 의 데이타를 가져온다. 여긴선 empno 다.
3) PRIOR empno = mgr  empno 을 mgr 로 사용하는 row 을 가져온다. 기존의 row 상위, 비교해서 가져온 row 하위에 있게 된다.
4) 이제 두번째로 가져온 row 에서 PRIOR empno = mgr 을 실행시킨다.
5) 이런 과정이 연속으로 반복되면서 최종적으로 가져온 data 는 트리 구조를 이루게 된다.(계층구조)
6) LEVEL 은 depth 을 의미한다.

7) empno = PRIOR mgr 한다면 가져온 row 의 mgr을 기준으로 비교하여 data을 가져오게 된다.
   예제 2 참조

 
---- 예제 1
SELECT                  LEVEL,empno,ename, mgr, job  --  LEVEL 은 depth 을 의미한다.
FROM                     emp
START WITH             job = 'PRESIDENT'   -- 직업이 PRESIDENT를 기준으로
CONNECT BY PRIOR empno = mgr -- 사원(empno)과 관리자(mgr)의  관계를 계층

 

-- level 을 공백으로 찍어 본다.

SELECT                  LPAD(' ', 4*(LEVEL-1)) || ename ename,LEVEL, empno, mgr, job 
FROM                     emp
START WITH             job = 'PRESIDENT'   -- 직업이 PRESIDENT를 기준으로
CONNECT BY PRIOR empno = mgr    -- 사원(empno)과 관리자(mgr)의  관계를 계층

-- 결과치
ENAME         LEVEL EMPNO MGR     JOB
KING             1 7839      PRESIDENT
    JONES         2 7566 7839 MANAGER
        SCOTT     3 7788 7566 ANALYST
            ADAMS 4 7876 7788 CLERK
        FORD     3 7902 7566 ANALYST
            SMITH 4 7369 7902 CLERK
        JJS         3 9000 7566 ANALIST
    BLAKE         2 7698 7839 MANAGER
        ALLEN     3 7499 7698 SALESMAN
        WARD     3 7521 7698 SALESMAN
        MARTIN     3 7654 7698 SALESMAN
        TURNER     3 7844 7698 SALESMAN
        JAMES     3 7900 7698 CLERK
    CLARK         2 7782 7839 MANAGER
        MILLER     3 7934 7782 CLERK


----예제 2 - PRIOR 위치 변경
SELECT           LPAD(' ', 4*(LEVEL-1)) || ename ename,LEVEL, empno, mgr, job  -- level 을 공백으로 찍어 본다.
FROM             emp
START WITH       job = 'CLERK'   -- 직업이 CLERK를 기준으로
CONNECT BY empno = PRIOR mgr -- 사원(empno)과 관리자(mgr)의  관계를 계층   


-- 결과치
ENAME             LEVEL EMPNO MGR     JOB
SMITH             1     7369 7902 CLERK
    FORD         2     7902 7566 ANALYST
        JONES     3     7566 7839 MANAGER
            KING 4     7839      PRESIDENT
ADAMS             1     7876 7788 CLERK
    SCOTT         2     7788 7566 ANALYST
        JONES     3     7566 7839 MANAGER
            KING 4     7839      PRESIDENT
JAMES             1     7900 7698 CLERK
    BLAKE         2     7698 7839 MANAGER
        KING     3     7839      PRESIDENT
MILLER             1     7934 7782 CLERK
    CLARK         2     7782 7839 MANAGER
        KING     3     7839      PRESIDENT


---- 예제 3: 조건 절 사용
-- 1) WHERE  절 사용    
SELECT           LPAD(' ', 4*(LEVEL-1)) || ename ename,LEVEL, empno, mgr, job  -- level 을 공백으로 찍어 본다.
FROM             emp
WHERE     ename LIKE '%K%'
START WITH       job = 'PRESIDENT'   -- 직업이 PRESIDENT를 기준으로
CONNECT BY PRIOR empno = mgr -- 사원(empno)과 관리자(mgr)의  관계를 계층

-- 2) CONNECT BY PRIOR 아래에 AND 사용
SELECT           LPAD(' ', 4*(LEVEL-1)) || ename ename,LEVEL, empno, mgr, job  -- level 을 공백으로 찍어 본다.
FROM             emp
START WITH       job = 'PRESIDENT'   -- 직업이 PRESIDENT를 기준으로
CONNECT BY PRIOR empno = mgr -- 사원(empno)과 관리자(mgr)의  관계를 계층
AND      ename LIKE '%K%'

-- 3) LEVEL 조건 사용
SELECT           LPAD(' ', 4*(LEVEL-1)) || ename ename, empno, mgr, job
FROM             emp
START WITH       job='PRESIDENT'
CONNECT BY PRIOR empno =mgr
AND              LEVEL <= 2

 

---- 예제4 :  각 LEVELl별로 급여의 합과 인원수를 구하는 예제
 
SELECT           LEVEL, SUM(sal) salTotal,COUNT(empno) empnCnt
FROM             emp
START WITH       job='PRESIDENT'
CONNECT BY PRIOR empno=mgr     
GROUP BY         LEVEL
ORDER BY         LEVEL


-- 결과치 
     LEVEL   salTotal    empnCnt
---------- ---------- ----------
         1       5000          1
         2       8275          3
         3      13850          8
         4       1900          2



부서별 인원 가로로 출력하기

SELECT     deptno, SUBSTR (MAX (SYS_CONNECT_BY_PATH (ename, ',')), 2) pathU
      FROM (SELECT ename, deptno,
                   ROW_NUMBER () OVER (PARTITION BY deptno ORDER BY ename) rnum
              FROM emp)
START WITH rnum = 1
CONNECT BY PRIOR rnum = rnum - 1 AND PRIOR deptno = deptno
GROUP BY deptno

sql 2009. 5. 19. 16:02

oracle9i ]start with connect by prior~


계층형 게시판 같이 트리구조로 되어 있는

디비를 삭제, 정렬, 기타 등등 트리구조에서 아주아주아~~~~~~~주 편하게 사용할 수 있다.

 글번호

그룹 

단계 

순서 

부모글 

 

 1

1

 3

  3 

 5

3

    5

 6

4

      6

 2

5

  2

 4

1

2

6

 2

    4

 

사용법 ) 3번글을 지우려한다면 그 하위 답변인 5,6번 게시물도 지워져야 한다.

            이때 쿼리 짜낼려고 별짓을 다 했는데; 간단한 방법이 있었으니..두둥~

            delete from s_board

            where 글번호 in (

                                     select *
                                     from s_board
                                     start with 글번호 = 3                          //시작하는 부분
                                     connect by prior 글번호 = 부모글        //검색 대상 을 트리형태로 검색 
                                     order by 그룹 desc, 순서

           )

           요롷게 start with ~ connect by prior 을 써주면 된다.

 

* prior의 위치

   - connect by prior 자식컬럼 =             부모컬럼 => 부모에서 자식으로 트리 구성

   - connect by        자식컬럼 = prior      부모컬럼 => 자식에서 부모로 트리 구성

 

 

 

참고 ) http://blog.naver.com/humanlinux?Redirect=Log&logNo=110005644075

         http://cafe.naver.com/ohsol.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=21

sql 2009. 2. 16. 15:04

oracle (+)을 left outer join on 으로 바꾸기

아래의 두개의 쿼리는 동일한 쿼리인다.
(+)를 left outer join on 으로 변경시킨 sql이다.

SELECT SV.STD_NO, SV.KOR_NM, SV.SUST, SV.DEPT_KOR_NM, SV.SHYR, SV.SHREG_CD, PG.PROGRAM, PC.PDCHECK
       , NVL(MB.YEAR, (CASE WHEN SUBSTR(SV.STD_NO, 0, 4) > 8000 THEN TO_NUMBER(1900+SUBSTR(SV.STD_NO, 0, 2)) ELSE TO_NUMBER(SUBSTR(SV.STD_NO, 0, 4)) END)) AS BASEYEAR
FROM ABK_PROGRAMGROUP PG
    , ABEEK_STUINFO_VIEW SV
    , ABK_ABEEKPDCHECK PC
    , ABK_MIDBASIS MB
WHERE 1=1
AND SV.ABEEK_FG = 'Y'
AND SV.SHREG_CD IN ('1','2')
AND PG.HAKKWACD = SV.SUST
AND SV.STD_NO = PC.HAKBUN(+)
AND SV.STD_NO = MB.HAKBUN(+)
ORDER BY SV.STD_NO DESC, SV.KOR_NM DESC



 

SELECT SV.STD_NO, SV.KOR_NM, SV.SUST, SV.DEPT_KOR_NM, SV.SHYR, SV.SHREG_CD, SV.PROGRAM, PC.PDCHECK
       , NVL(MB.YEAR, (CASE WHEN SUBSTR(SV.STD_NO, 0, 4) > 8000 THEN TO_NUMBER(1900+SUBSTR(SV.STD_NO, 0, 2)) ELSE TO_NUMBER(SUBSTR(SV.STD_NO, 0, 4)) END)) AS BASEYEAR
FROM
(SELECT  S.STD_NO, S.KOR_NM, S.SUST, S.DEPT_KOR_NM, S.SHYR, S.SHREG_CD, PG.PROGRAM
 FROM ABEEK_STUINFO_VIEW S, ABK_PROGRAMGROUP PG
WHERE S.ABEEK_FG = 'Y'
 AND S.SHREG_CD IN ('1','2')
 AND S.SUST = PG.HAKKWACD
)SV
LEFT OUTER JOIN
(SELECT HAKBUN, PDCHECK
FROM ABK_ABEEKPDCHECK
)PC
ON SV.STD_NO = PC.HAKBUN
LEFT OUTER JOIN
(SELECT HAKBUN, YEAR
 FROM ABK_MIDBASIS
)MB
ON SV.STD_NO = MB.HAKBUN
ORDER BY SV.STD_NO DESC, SV.KOR_NM DESC

sql 2009. 2. 5. 10:16

ORACLE9I] DECODE, CASE WHEN THEN ELSE END

조건절 필요시 사용할수있는 함수들...

===================================================================================================================
DECODE(기준컬럼,조건값1,변경값1,조건값2,변경값2) AS 컬럼명


SELECT deptno,
                    DECODE(deptno, 10 , 'ACCOUNTING' ,
                                             20 , 'RESEARCH' ,
                                             30 , 'SALES' ,
                                             40 , 'OPERATIONS')
 FROM emp ;
===================================================================================================================


===================================================================================================================
CASE WHEN  조건절 THEN TRUE일경우값 ELSE FALSE 일경우값 END AS 컬럼명
CASE 기준컬럼 WHEN  조건값 THEN TRUE일경우값 ELSE FALSE 일경우값 END AS 컬럼명

EX>
SELECT DEPTNO, CASE WHEN DEPTNO='10' THEN '10번부서' ELSE (CASE WHEN DEPTNO='20' THEN '20번부서' ELSE '30번부서' END)  END AS DEPTRENAME
FROM EMP
===================================================================================================================


sql 2009. 1. 6. 22:12

ROWNUM의 동작 원리와 활용 방법 - 꼭 읽어보길 -

ROWNUM의 동작 원리와 활용 방법

ROWNUM은 오라클 데이터베이스가 제공하는 마술과도 같은 컬럼입니다. 이 때문에 많은 사용자들이 문제를 겪기도 합니다. 하지만 그 원리와 활용 방법을 이해한다면 매우 유용하게 사용할 수 있습니다. 필자는 주로 두 가지 목적으로 ROWNUM을 사용합니다.

Top-N 프로세싱: 이 기능은 다른 일부 데이터베이스가 제공하는 LIMIT 구문과 유사합니다.
쿼리 내에서의 페이지네이션(pagination) – 특히 웹과 같은 "stateless" 환경에서 자주 활용됩니다. 필자는 asktom.oracle.com 웹 사이트에서도 이 테크닉을 사용하고 있습니다.

두 가지 활용 방안을 설명하기 전에, 먼저 ROWNUM의 동작 원리에 대해 살펴 보기로 하겠습니다

ROWNUM의 동작 원리

ROWNUM은 쿼리 내에서 사용 가능한 (실제 컬럼이 아닌) 가상 컬럼(pseudocolumn)입니다. ROWNUM에는 숫자 1, 2, 3, 4, ... N의 값이 할당됩니다. 여기서 N 은 ROWNUM과 함께 사용하는 로우의 수를 의미합니다. ROWNUM의 값은 로우에 영구적으로 할당되지 않습니다(이는 사람들이 많이 오해하는 부분이기도 합니다). 테이블의 로우는 숫자와 연계되어 참조될 수 없습니다. 따라서 테이블에서 "row 5"를 요청할 수 있는 방법은 없습니다. "row 5"라는 것은 존재하지 않기 때문입니다.

또 ROWNUM 값이 실제로 할당되는 방법에 대해서도 많은 사람들이 오해를 하고 있습니다. ROWNUM 값은 쿼리의 조건절이 처리되고 난 이후, 그리고 sort, aggregation이 수행되기 이전에 할당됩니다. 또 ROWNUM 값은 할당된 이후에만 증가(increment) 됩니다. 따라서 아래 쿼리는 로우를 반환하지 않습니다.


select *
  from t
 where ROWNUM > 1;


첫 번째 로우에 대해 ROWNUM > 1의 조건이 True가 아니기 때문에, ROWNUM은 2로 증가하지 않습니다. 아래와 같은 쿼리를 생각해 봅시다.


select ..., ROWNUM
  from t
 where <where clause>
 group by <columns>
having <having clause>
 order by <columns>;


이 쿼리는 다음과 같은 순서로 처리됩니다.

1. FROM/WHERE 절이 먼저 처리됩니다.
2. ROWNUM이 할당되고 FROM/WHERE 절에서 전달되는 각각의 출력 로우에 대해 증가(increment) 됩니다.
3. SELECT가 적용됩니다.
4. GROUP BY 조건이 적용됩니다.
5. HAVING이 적용됩니다.
6. ORDER BY 조건이 적용됩니다
.

따라서 아래와 같은 쿼리는 에러가 발생할 수 밖에 없습니다.


select *
  from emp
 where ROWNUM <= 5
 order by sal desc;


이 쿼리는 가장 높은 연봉을 받는 다섯 명의 직원을 조회하기 위한 Top-N 쿼리로 작성되었습니다. 하지만 실제로 쿼리는 5 개의 레코드를 랜덤하게(조회되는 순서대로) 반환하고 salary를 기준으로 정렬합니다. 이 쿼리를 위해서 사용되는 가상코드(pseudocode)가 아래와 같습니다.


ROWNUM = 1
for x in
( select * from emp )
loop
    exit when NOT(ROWNUM <= 5)
    OUTPUT record to temp
    ROWNUM = ROWNUM+1
end loop
SORT TEMP


위에서 볼 수 있듯 처음의 5 개 레코드를 가져 온후 바로 sorting이 수행됩니다. 쿼리에서 "WHERE ROWNUM = 5" 또는 "WHERE ROWNUM > 5"와 같은 조건은 의미가 없습니다. 이는 ROWNUM 값이 조건자(predicate) 실행 과정에서 로우에 할당되며, 로우가 WHERE 조건에 의해 처리된 이후에만 increment 되기 때문입니다.

올바르게 작성된 쿼리가 아래와 같습니다.


select *
  from 
( select *
    from emp
   order by sal desc )
 where ROWNUM <= 5;


위 쿼리는 salary를 기준으로 EMP를 내림차순으로 정렬한 후, 상위의 5 개 레코드(Top-5 레코드)를 반환합니다. 아래에서 다시 설명되겠지만, 오라클 데이터베이스가 실제로 전체 결과 셋을 정렬하지 않습니다. (오라클 데이터베이스는 좀 더 지능적인 방식으로 동작합니다.) 하지만 사용자가 얻는 결과는 동일합니다.



ROWNUM을 이용한 Top-N 쿼리 프로세싱

일반적으로 Top-N 쿼리를 실행하는 사용자는 다소 복잡한 쿼리를 실행하고, 그 결과를 정렬한 뒤 상위의 N 개 로우만을 반환하는 방식을 사용합니다. ROWNUM은 Top- N쿼리를 위해 최적화된 기능을 제공합니다. ROWNUM을 사용하면 대량의 결과 셋을 정렬하는 번거로운 과정을 피할 수 있습니다. 먼저 그 개념을 살펴보고 예제를 통해 설명하기로 하겠습니다.

아래와 같은 쿼리가 있다고 가정해 봅시다.


select ...
  from ...
 where ...
 order by columns;


또 이 쿼리가 반환하는 데이터가 수천 개, 수십만 개, 또는 그 이상에 달한다고 가정해 봅시다. 하지만 사용자가 실제로 관심 있는 것은 상위 N개(Top 10, Top 100)의 값입니다. 이 결과를 얻기 위한 방법에는 두 가지가 있습니다.


클라이언트 애플리케이션에서 쿼리를 실행하고 상위 N 개의 로우만을 가져오도록 명령
• 쿼리를 인라인 뷰(inline view)로 활용하고, ROWNUM을 이용하여 결과 셋을 제한 (예: SELECT * FROM (your_query_here) WHERE ROWNUM <= N)

두 번째 접근법은 첫 번째에 비해 월등한 장점을 제공합니다. 그 이유는 두 가지입니다. 첫 번째로, ROWNUM을 사용하면 클라이언트의 부담이 줄어듭니다. 데이터베이스에서 제한된 결과 값만을 전송하기 때문입니다. 두 번째로, 데이터베이스에서 최적화된 프로세싱 방법을 이용하여 Top N 로우를 산출할 수 있습니다. Top-N 쿼리를 실행함으로써, 사용자는 데이터베이스에 추가적인 정보를 전달하게 됩니다. 그 정보란 바로 "나는N 개의 로우에만 관심이 있고, 나머지에 대해서는 관심이 없다"는 메시지입니다. 이제, 정렬(sorting) 작업이 데이터베이스 서버에서 어떤 원리로 실행되는지 설명을 듣고 나면 그 의미를 이해하실 수 있을 것입니다. 샘플 쿼리에 위에서 설명한 두 가지 접근법을 적용해 보기로 합시다.


select *
  from t
 order by unindexed_column;


여기서 T가 1백만 개 이상의 레코드를 저장한 큰 테이블이라고, 그리고 각각의 레코드가 100 바이트 이상으로 구성되어 있다고 가정해 봅시다. 그리고 UNINDEXED_COLUMN은 인덱스가 적용되지 않은 컬럼이라고, 또 사용자는 상위 10 개의 로우에만 관심이 있다고 가정하겠습니다. 오라클 데이터베이스는 아래와 같은 순서로 쿼리를 처리합니다.

1. T에 대해 풀 테이블 스캔을 실행합니다.
2. UNINDEXED_COLUMN을 기준으로 T를 정렬합니다. 이 작업은 "full sort"로 진행됩니다.
3. Sort 영역의 메모리가 부족한 경우 임시 익스텐트를 디스크에 스왑하는 작업이 수행됩니다.
4. 임시 익스텐트를 병합하여 상위 10 개의 레코드를 확인합니다.
5.쿼리가 종료되면 임시 익스텐트에 대한 클린업 작업을 수행합니다. .

결과적으로 매우 많은 I/O 작업이 발생합니다. 오라클 데이터베이스가 상위 10 개의 로우를 얻기 위해 전체 테이블을 TEMP 영역으로 복사했을 가능성이 높습니다.

그럼 다음으로, Top-N 쿼리를 오라클 데이터베이스가 개념적으로 어떻게 처리할 수 있는지 살펴 보기로 합시다.


select *
  from
(select *
   from t
  order by unindexed_column)
 where ROWNUM < :N;


오라클 데이터베이스가 위 쿼리를 처리하는 방법이 아래와 같습니다.

1. 앞에서와 마찬가지로 T에 대해 풀-테이블 스캔을 수행합니다(이 과정은 피할 수 없습니다).
2. :N 엘리먼트의 어레이(이 어레이는 메모리에 저장되어 있을 가능성이 높습니다)에서 :N 로우만을 정렬합니다.

상위N 개의 로우는 이 어레이에 정렬된 순서로 입력됩니다. N +1 로우를 가져온 경우, 이 로우를 어레이의 마지막 로우와 비교합니다. 이 로우가 어레이의 N +1 슬롯에 들어가야 하는 것으로 판명되는 경우, 로우는 버려집니다. 그렇지 않은 경우, 로우를 어레이에 추가하여 정렬한 후 기존 로우 중 하나를 삭제합니다. Sort 영역에는 최대 N 개의 로우만이 저장되며, 따라서 1 백만 개의 로우를 정렬하는 대신N 개의 로우만을 정렬하면 됩니다.

이처럼 간단한 개념(어레이의 활용, N개 로우의 정렬)을 이용하여 성능 및 리소스 활용도 면에서 큰 이익을 볼 수 있습니다. (TEMP 공간을 사용하지 않아도 된다는 것을 차치하더라도) 1 백만 개의 로우를 정렬하는 것보다 10 개의 로우를 정렬하는 것이 메모리를 덜 먹는다는 것은 당연합니다.

아래의 테이블 T를 이용하면, 두 가지 접근법이 모두 동일한 결과를 제공하지만 사용되는 리소스는 극적인 차이를 보임을 확인할 수 있습니다.


create table t
as
select dbms_random.value(1,1000000)
id,
       rpad('*',40,'*' ) data
  from dual
connect by level <= 100000;

begin
dbms_stats.gather_table_stats
( user, 'T');
end;
/

Now enable tracing, via

exec
dbms_monitor.session_trace_enable
(waits=>true);


And then run your top-N query with ROWNUM:


select *
  from
(select *
   from t
  order by id)
where rownum <= 10;
 

마지막으로 상위 10 개의 레코드만을 반환하는 쿼리를 실행합니다.


declare
cursor c is
select *
  from t
 order by id;
l_rec c%rowtype;
begin
    open c;
    for i in 1 .. 10
    loop
        fetch c into l_rec;
        exit when c%notfound;
    end loop;
    close c;
end;
/


이 쿼리를 실행한 후, TKPROF를 사용해서 트레이스 결과를 확인할 수 있습니다. 먼저 Top-N 쿼리 수행 후 확인한 트레이스 결과가 Listing 1과 같습니다.

Code Listing 1: ROWNUM을 이용한 Top-N 쿼리


select *
  from
(select *
   from t
  order by id)
where rownum <= 10

call         count     cpu elapsed   disk     query      current    rows
--------     --------  -------  -------   -------  --------   --------   ------
Parse        1         0.00     0.00      0          0        0           0
Execute      1         0.00     0.00      0          0        0           0
Fetch        2         0.04     0.04      0        949        0          10
--------     --------  -------  -------   -------  --------   --------   ------
total        4         0.04     0.04      0        949        0          10

Rows                         Row          Source Operation
-----------------            ---------------------------------------------------
10                           COUNT STOPKEY (cr=949 pr=0 pw=0 time=46997 us)
10                           VIEW  (cr=949 pr=0 pw=0 time=46979 us)
10                           SORT ORDER BY STOPKEY (cr=949 pr=0 pw=0 time=46961 us)
100000                       TABLE ACCESS FULL T (cr=949 pr=0 pw=0 time=400066 us)


이 쿼리는 전체 테이블을 읽어 들인 후, SORT ORDER BY STOPKEY 단계를 이용해서 임시 공간에서 사용되는 로우를 10 개로 제한하고 있습니다. 마지막 Row Source Operation 라인을 주목하시기 바랍니다. 쿼리가 949 번의 논리적 I/O를 수행했으며(cr=949), 물리적 읽기/쓰기는 전혀 발생하지 않았고(pr=0, pw=0), 불과 400066 백만 분의 일초 (0.04 초) 밖에 걸리지 않았습니다. 이 결과를 Listing 2의 실행 결과와 비교해 보시기 바랍니다.

Code Listing 2: ROWNUM을 사용하지 않은 쿼리


SELECT * FROM T ORDER BY ID
call         count     cpu elapsed   disk     query      current    rows
--------     --------  -------  -------   -------  --------   --------   ------
Parse         1        0.00     0.00        0        0        0           0
Execute       2        0.00     0.00        0        0        0           0
Fetch        10        0.35     0.40      155      949        6          10
--------     --------  -------  -------   -------  --------   --------   ------
total        13        0.36     0.40      155      949        6          10

Rows                         Row          Source Operation
-----------------            ---------------------------------------------------
10                           SORT ORDER BY (cr=949 pr=155 pw=891 time=401610 us)
100000                       TABLE ACCESS FULL T (cr=949 pr=0 pw=0 time=400060 us)

Elapsed times include waiting for the following events:

Event waited on                  Times
------------------------------   ------------
direct path write temp           33
direct path read temp             5


결과가 완전히 다른 것을 확인하실 수 있습니다. "elapsed/CPU time"이 크게 증가했으며, 마지막 Row Source Operation 라인을 보면 그 이유를 이해할 수 있습니다. 정렬 작업은 디스크 상에서 수행되었으며, 물리적 쓰기(physical write) 작업이 "pw=891"회 발생했습니다. 또 다이렉트 경로를 통한 읽기/쓰기 작업이 발생했습니다. (10 개가 아닌) 100,000 개의 레코드가 디스크 상에서 정렬되었으며, 이로 인해 쿼리의 실행 시간과 런타임 리소스가 급증하였습니다.


ROWNUM을 이용한 페이지네이션

필자가 ROWNUM을 가장 즐겨 사용하는 대상이 바로 페이지네이션(pagination)입니다. 필자는 결과 셋의 로우 N 에서 로우 M까지를 가져오기 위해 ROWNUM을 사용합니다. 쿼리의 일반적인 형식이 아래와 같습니다.


select *
  from ( select /*+ FIRST_ROWS(n) */
  a.*, ROWNUM rnum
      from ( your_query_goes_here,
      with order by ) a
      where ROWNUM <=
      :MAX_ROW_TO_FETCH )
where rnum  >= :MIN_ROW_TO_FETCH;

where


여기서,

FIRST_ROWS(N)는 옵티마이저에게 "나는 앞부분의 로우에만 관심이 있고, 그 중 N 개를 최대한 빨리 가져오기를 원한다"는 메시지를 전달하는 의미를 갖습니다.
:MAX_ROW_TO_FETCH는 결과 셋에서 가져올 마지막 로우로 설정됩니다. 결과 셋에서 50 번째 – 60 번째 로우만을 가져오려 한다면 이 값은 60이 됩니다.
:MIN_ROW_TO_FETCH는 결과 셋에서 가져올 첫 번째 로우로 설정됩니다. 결과 셋에서 50 번째 – 60 번째 로우만을 가져오려 한다면 이 값은 50이 됩니다.

이 시나리오는 웹 브라우저를 통해 접속한 사용자가 검색을 마치고 그 결과를 기다리고 있는 상황을 가정하고 있습니다. 따라서 첫 번째 결과 페이지(그리고 이어서 두 번째, 세 번째 결과 페이지)를 최대한 빨리 반환해야 할 것입니다. 쿼리를 자세히 살펴 보면, (처음의 :MAX_ROW_TO_FETCH 로우를 반환하는) Top-N 쿼리가 사용되고 있으며, 따라서 위에서 설명한 최적화된 기능을 이용할 수 있음을 알 수 있습니다. 또 네트워크를 통해 클라이언트가 관심을 갖는 로우만을 반환하며, 조회 대상이 아닌 로우는 네트워크로 전송되지 않습니다.

페이지네이션 쿼리를 사용할 때 주의할 점이 하나 있습니다. ORDER BY 구문은 유니크한 컬럼을 대상으로 적용되어야 합니다. 유니크하지 않은 컬럼 값을 대상으로 정렬을 수행해야 한다면 ORDER BY 조건에 별도의 조건을 추가해 주어야 합니다. 예를 들어 SALARY를 기준으로 100 개의 레코드를 정렬하는 상황에서 100 개의 레코드가 모두 동일한 SALARY 값을 갖는다면, 로우의 수를 20-25 개로 제한하는 것은 의미가 없을 것입니다. 여러 개의 중복된 ID 값을 갖는 작은 테이블을 예로 들어 설명해 보겠습니다.


SQL> create table t
  2  as
  3  select mod(level,5) id,
     trunc(dbms_random.value(1,100)) data
  4    from dual
  5  connect by level <= 10000;
Table created.


ID 컬럼을 정렬한 후 148-150 번째 로우, 그리고 148–151 번째 로우를 쿼리해 보겠습니다.


SQL> select *
  2    from
  3  (select a.*, rownum rnum
  4     from
  5  (select id, data
  6     from t
  7   order by id) a
  8   where rownum <= 150
  9  )
 10   where rnum >= 148;

 ID           DATA           RNUM
-------       ----------     -----------
0             38             148
0             64             149
0             53             150

SQL>
SQL> select *
  2    from
  3  (select a.*, rownum rnum
  4     from
  5  (select id, data
  6     from t
  7   order by id) a
  8   where rownum <= 151
  9  )
 10   where rnum >= 148;

 ID           DATA           RNUM
-------       ----------     -----------
0             59             148
0             38             149
0             64             150
0             53             151


로우 148의 경우 DATA=38의 결과가 반환되었습니다. 두 번째 쿼리에서는 DATA=59의 결과가 반환되었습니다. 두 가지 쿼리 모두 올바른 결과를 반환하고 있습니다. 쿼리는 데이터를 ID 기준으로 정렬한 후 앞부분의 147 개 로우를 버린 후 그 다음의 3 개 또는 4 개의 로우를 반환합니다. 하지만 ID에 중복값이 너무 많기 때문에, 쿼리는 항상 동일한 결과를 반환함을 보장할 수 없습니다. 이 문제를 해결하려면 ORDER BY 조건에 유니크한 값을 추가해 주어야 합니다. 위의 경우에는 ROWID를 사용하면 됩니다.


SQL> select *
  2    from
  3  (select a.*, rownum rnum
  4     from
  5  (select id, data
  6     from t
  7   order by id, rowid) a
  8   where rownum <= 150
  9  )
 10   where rnum >= 148;

 ID           DATA           RNUM
-------       ----------     -----------
0             45             148
0             99             149
0             41             150

SQL>
SQL> select *
  2    from
  3  (select a.*, rownum rnum
  4     from
  5  (select id, data
  6     from t
  7   order by id, rowid) a
  8   where rownum <= 151
  9  )
 10   where rnum >= 148;

 ID           DATA           RNUM
-------       ----------     -----------
0             45             148
0             99             149
0             41             150
0             45             151


이제 쿼리를 반복 실행해도 동일한 결과를 보장할 수 있게 되었습니다. ROWID는 테이블 내에서 유니크한 값을 가집니다. 따라서 ORDER BY ID 조건과 ORDER BY ROWID 기준을 함께 사용함으로써 사용자가 기대한 순서대로 페이지네이션 쿼리의 결과를 확인할 수 있습니다. 다음 단계

ASK Tom
오라클 부사장 Tom Kyte가 까다로운 기술적 문제에 대한 답변을 제공해 드립니다. 포럼의 하이라이트 정보를 Tom의 컬럼에서 확인하실 수 있습니다.
asktom.oracle.com

추가 자료:
Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions
Effective Oracle By Design
 

ROWNUM 개념 정리

지금까지 ROWNUM에 관련하여 아래와 같은 개념을 설명하였습니다.


ROWNUM의 할당 원리와 잘못된 쿼리 작성을 피하는 방법
ROWNUM이 쿼리 프로세싱에 미치는 영향과 웹 환경의 페이지네이션을 위한 활용 방안
ROWNUM을 이용하여 Top N쿼리로 인한 TEMP 공간의 사용을 피하고 쿼리 응답 속도를 개선하는 방법 


sql 2009. 1. 6. 21:38

Oracle 날짜 데이터


날짜 데이터는 DAY단위로 사칙연산된다.

그러면 SYSDATE - 1 은 하루전, 1/24은 1시간전, 1/1440은 1분전이다.

# SYSDATE -- 현시간

# ADD_MONTHS(DATE, 1) -- 한달 더하기

# ADD_MONTHS(DATE, -13) -- 13개월 빼기

# LAST_DAY(SYSDATE)  --이번달의 마지막 날짜를 조회한다.

# MONTHS_BETWEEN(SYSDATE, BIRTH_DATE) -- 현재 날짜와 생일과의 개월 간격 

# NEXT_DAY(SYSDATE,2) # -- 현재 날짜 다음의 월요일(2)

# ROUND(SYSDATE,'YEAR') -- 현재 날짜를 연도단위로 반올림

# TRUNC(SYSDATE, 'DAY') -- 현재 날짜를 DAY단위로 절삭


EX>

하루더하기

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

Select sysdate + 1 from dual

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

 

시간더히기

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

Select sysdate + 1/24 from dual

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

 

분더하기

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

Select sysdate + 1/(24*60) from dual

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

 

초더히기

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

Select sysdate + 1/(24*60*60) from dual

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