[ORACLE] START WITH, CONNECT BY - 계층적쿼리
원글 URL : http://stillrabbit.blogspot.com/2008/01/oracle-start-with-connect-by-prior.html
START WITH와 CONNECT BY 는 오라클에서만 가능한 함수로서 데이터를 계층구조로 가져올 수 있다.
◈ START WITH
- 계층 질의의 루트(부모행)로 사용될 행을 지정 합니다..
- 서브쿼리를 사용할 수도 있습니다.
◈ CONNECT BY
- 이 절을 이용하여 계층 질의에서 상위계층(부모행)과 하위계층(자식행)의 관계를 규정 합니다.
- 보통 PRIOR 연산자를 많이 사용 합니다..
- 서브쿼리를 사용할 수 없습니다..
- PRIOR이 붙는 column 이 가져온 row 의 column을 의미한다. 즉 상위에 존재할 데이타가 되게 된다. 어느쪽에 붙느냐 잘 따져 본다.
◈ CONNECT BY의 실행순서는 다음과 같습니다.
- 첫째 START WITH절
- 둘째 CONNECT BY 절
- 세째 WHERE 절 순서로 풀리게 되어있습니다.
◈ SYNTEX
SELECT FROM START WITH CONNECT BY PRIOR AND ORDER SIBLINGS BYor
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으로 표현하기가 어렵 습니다.
◈ 예제
SELECT A.BBSSEQ, LPAD(' ', LEVEL * 4) || A.title FROM ( SELECT * FROM KBBS02MT WHERE SITECD = 'ART' AND BBSCD='AR2' ) A -- START WITH UppBBsSeq IN (select UppBBsSeq='0' or UppBBsSeq='1') START WITH UppBBsSeq = '0' //일종의 WHERE절. UppBBsSeq가 0인 레코드부터 계층적 쿼리 수행 CONNECT BY PRIOR BbsSeq = UppBbsSeq //CONNECT BY - 각각의 행들이 어떻게 연결되어야 하는지(JOIN) 정보를 작성한다. //PRIOR - BbsSeq를 참조하는 UppBbsSeq 컬럼이 속한 레코드를 모두 찾아라.(하위seq를 찾을 수 있다)
◈ LEVEL 키워드 - 오라클에서 실행되는 쿼리내 ROWNUM과 같은 가상컬럼이다.
SELECT A.BBSSEQ, LPAD(' ', LEVEL * 4) || A.title FROM ( SELECT * FROM KBBS02MT WHERE SITECD = 'ART' AND BBSCD='AR2' ) A START WITH UppBBsSeq = '0' CONNECT BY PRIOR BbsSeq = UppBbsSeq AND LEVEL < 3 //CONNECT BY 절내에 위치
'DBMS > ORACLE' 카테고리의 다른 글
[ORACLE] DROP TABLE (0) | 2009.11.13 |
---|---|
ORACLE ROWNUM의 동작 원리와 활용 방법 (0) | 2009.10.21 |
ORACLE rownum 설명.. 그리고 hint 절을 사용하자.. 게시판 페이지처리 (0) | 2009.10.20 |
[ORACLE] DROP TABLE
오라클 10G이후 부터 table을 DROP할경우 바로 삭제되는 것이 아니라 휴지통으로 보내버린다.
이를 휴지통으로 보내지 않고 바로 삭제 할려면 purge옵션을 추가 하여야 한다.
DROP TABLE tablename PURGE
휴지통을 완전히 비우는 SQL문
PURGE RECYCLEBIN
'DBMS > ORACLE' 카테고리의 다른 글
[ORACLE] START WITH, CONNECT BY - 계층적쿼리 (0) | 2009.12.15 |
---|---|
ORACLE ROWNUM의 동작 원리와 활용 방법 (0) | 2009.10.21 |
ORACLE rownum 설명.. 그리고 hint 절을 사용하자.. 게시판 페이지처리 (0) | 2009.10.20 |
ORACLE ROWNUM의 동작 원리와 활용 방법
오라클 전문가 Tom Kyte가 ROWNUM의 동작 원리와 활용 방법에 대해 설명합니다.
이번 호의 Ask Tom 컬럼은 지금까지와는 조금 다른 내용을 담고 있습니다. 필자는 오라클 데이터베이스에서 Top-N 쿼리와 페이지네이션(pagination) 쿼리를 구현하는 방법에 대해 자주 질문을 받곤 합니다. 하나의 컬럼을 통해 이러한 질문에 한꺼번에 대답하기 위한 방편으로, < Effective Oracle by Design (Oracle Press, 2003)> 의 내용을 인용하기로 했습니다. 컬럼의 포맷에 맞게 책의 내용이 다소 수정되었음을 참고하시기 바랍니다.
결과 셋의 제한
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 값을 갖는 작은 테이블을 예로 들어 설명해 보겠습니다.
create table t as select mod(level,5) id, trunc(dbms_random.value(1,100)) data from dual connect by level <= 10000; Table created.
ID 컬럼을 정렬한 후 148-150 번째 로우, 그리고 148–151 번째 로우를 쿼리해 보겠습니다.
SQL> select * from (select a.*, rownum rnum from (select id, data from t order by id) a where rownum <= 150 ) where rnum >= 148;
ID DATA RNUM ------- ---------- ----------- 0 38 148 0 64 149 0 53 150
select * from (select a.*, rownum rnum from (select id, data from t order by id) a where rownum <= 151 ) 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 * from (select a.*, rownum rnum from (select id, data from t order by id, rowid) a where rownum <= 150 ) where rnum >= 148;
ID DATA RNUM ------- ---------- ----------- 0 45 148 0 99 149 0 41 150
select * from (select a.*, rownum rnum from (select id, data from t order by id, rowid) a where rownum <= 151 ) 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 기준을 함께 사용함으로써 사용자가 기대한 순서대로 페이지네이션 쿼리의 결과를 확인할 수 있습니다.
ROWNUM 개념 정리
지금까지 ROWNUM에 관련하여 아래와 같은 개념을 설명하였습니다.
- ROWNUM의 할당 원리와 잘못된 쿼리 작성을 피하는 방법
- ROWNUM이 쿼리 프로세싱에 미치는 영향과 웹 환경의 페이지네이션을 위한 활용 방안
- ROWNUM을 이용하여 Top N쿼리로 인한 TEMP 공간의 사용을 피하고 쿼리 응답 속도를 개선하는 방법
'DBMS > ORACLE' 카테고리의 다른 글
[ORACLE] DROP TABLE (0) | 2009.11.13 |
---|---|
ORACLE rownum 설명.. 그리고 hint 절을 사용하자.. 게시판 페이지처리 (0) | 2009.10.20 |
Dynamic SQL의 사용 (0) | 2009.10.06 |
ORACLE rownum 설명.. 그리고 hint 절을 사용하자.. 게시판 페이지처리
오라클에서 rownum 은 쿼리가 실행될 때 결과 레코드에 번호를 나타내어 주는 필드이다.
물론 table을 만들 때 rownum을 만들어줄 필요는 없다.
어떠한 테이블이라도
select rownum from boardtable의 형태로 쿼리를 날리면 레코드에 번호가 나타내어 주는 것을 볼 수 있을 것이다...
따라서 페이징을 위한 쿼리에서 우리는 rownum이 10 보다 크고 20보다 작은...
이런 식의 조건을 주어.. 원하는 범위의 레코드만 쿼리 할 수 있다..
select rownum from boardtable그래서 위와 같이 쿼라하면 어떨까?
결과가 하나도 않나올 것이다.. 왜냐하면.. 쿼리가 실행되면 where 절 부분이 먼저 실행 될 것이고.. select 를 하기전에는 rownum이 없기 때문이다.
따라서, rownum을 긁어오는 쿼리를 한번 미리 해줄 필요가 잇다..
select * from (select rownum numrow , boardtable.* from boardtable) where numrow > 10 and numrow <= 20order by 절을 사용하지 않는다면 이것만으로 우리는 행복할 수 있다.
select * from (select rownum numrow , boardtable.* from boardtable order by reg_date desc) where numrow > 10 and numrow <= 20결과는 원하는 대로 되지 않음을 볼 수 있다.
이유는 아마도.... order by 가 rownum 값을 만드는데 영향을 못주는 것이라고 생각되는데.. 즉.. order by를 지정한다고 해도.. rownum은 최초의 order by 가 없는 상태로 만들어진다고 추측할 수 있다..
select rownum numrow , boardtable.* from boardtable order by reg_date desc select rownum numrow , boardtable.* from boardtable위의 두 쿼리는 실제로 rownum 값을 똑같이 만들어준다.. 물론 뿌리는 순서는 다르지만..
따라서 원하는대로 정렬을 할려면..
select rownum numrow , aa.* from ( select * from boardtable order by reg_date desc ) aa위의 형태로 쿼리를 바꾸어 줘야 한다.
그런데 또 우리는 범위를 지정해야 하니까..
select rownum numrow , aa.* from ( select * from boardtable order by reg_date desc ) aa where numrow > 10 and numrow <= 20하면 아무것도 안나오는 이유는 이미 설명했고..
select rownum numrow , aa.* from ( select * from boardtable order by reg_date desc ) aa where numrow > 10 and numrow <= 20따라서...
select * from ( select rownum numrow , aa.* from ( select * from boardtable order by reg_date desc ) aa ) where numrow > 10 and numrow <= 20위와 같이 해주면 되겟다..
그런데 좀 복잡하다.. 더구나 검색을 한번... 두번... 세번 을 하니... 데이타 양이 많다면 쿼리실행속도가 느려질 가능성이 있다.. 물론 속도에 가장 큰 영향을 주는 것은 order by 이지만 ... 그래서 index를 만들고 hint 절을 써야할 이유가 생기는 것이다.. 인덱스는 order by 를 하는 필드에 주면 된다.. 따라서.. reg_date 필드에 인덱스를 주면 되고... 그러나 hint 절을 사용하려면 유니크 속성이 있어야 한다... 따라서.. 인덱스를 줄 때 reg_date 필드와 프라이머리 키 필드를 같이 걸어주면 유닉속성을 이용할 수 있다..
create unique index idx_board_reg_date on boardtable (reg_date, idx)인덱스가 생성된다..
select /*+ index_desc(boardtable idx_board_reg_date) */ rownum, tvulist.* from tvulistorder by 없이 rownum이 제대로 긁어지나?
select * from (select /*+ index_desc(tvulist idx_board_reg_date) */ rownum as numrow, boardtable.* from boardtable ) where numrow > 10 and numrow <= 20이제 위와 같이하면... 원하는 범위의 데이타를 가져올 수 있겠다..
만일 인덱스를 사용하지 않앗는데... 데이타가 튀어나오는 시간이 좀 늦는다 싶으면.. 인덱스를 사용하라.. 행복할 것이다..
'DBMS > ORACLE' 카테고리의 다른 글
[ORACLE] DROP TABLE (0) | 2009.11.13 |
---|---|
ORACLE ROWNUM의 동작 원리와 활용 방법 (0) | 2009.10.21 |
Dynamic SQL의 사용 (0) | 2009.10.06 |
Dynamic SQL의 사용
보통의 어플리케이션 프로그램에서는 SQL문이 확정된 후 프로그램에 적용한다. 그러나, 다이나믹 SQL의 사용하는 경우는 사용자의 입력에 의거해서 SQL문이 작성되거나 다른 STATIC SQL문의 결과에 의해서 SQL문이 생성되는 경우 다이나믹SQL을 사용하게 된다.
- 비교될 칼럼이 변경되는 경우(WHERE절),
- 참조할 테이블이 변경되어야 하는 경우,
- INSERT, UPDATE시의 칼럼이 변경되는 경우
1. QUERY문이 아니면서 호스트 변수가 없는 경우
2. QUERY문이 아니면서 호스트 변수가 정해지지 않은 경우
3. 호스트 변수와 SELECT칼럼이 정해진 QUERY문인 경우
4. 호스트 변수와 SELECT칼럼이 정해지지 않은 QUERY문인 경우
▩ Method 1
이 방법은 Dynamic SQL문을 작성하고 “EXECUTE IMMEDIATE”를 사용하여 즉시 실행한다. SQL문은 QUERY문이 아니어야 하고 - (SELECT문)- 입력용 호스트 변수가 존재하지 않아야 한다. Method 1은 SQL문이 매번 실행될 때마다 PARSING을 한다.
'DELETE FROM EMP WHERE DEPTNO = 20' 'GRANT SELECT ON EMP TO scott'
▩ Method 2
이 방법은 Dynamic SQL문을 작성한 후 “PREPARE”와 “EXECUTE” 명령에 의해 실행한다. SQL문은 QUERY문이 아니어야 하고, 여기에서 사용된 호스트변수는 프리 컴파일 시점에서는 데이터 타입과 위치는 정해져 있어야 한다.
INSERT INTO EMP (ENAME, JOB) VALUES (:emp_name, :job_title) DELETE FROM EMP WHERE EMPNO = :emp_number
이 방법에서는 SQL문은 단 한번만 PARSING하게 되고 호스트 변수의 값을 달리하고 여러 번 실행시킬 수 있다. CREATE나 GRANT같은 DDL 문은 PREPAREd된 후에 실행 할 수 있다.
▩ Method 3
이 방법은 Dynamic SQL문을 작성한 후 “PREPARE”, “DECALRE”, "OPEN", "FETCH", "CLOSE" 와 같은 커서 명령으로 실행한다. SELECT-LIST와 입력 호스트변수의 데이터타입과 위치는 프리 컴파일 시점에서는 정해져야 한다. 이 방법은 query문이어야 한다.
SELECT DEPTNO, MIN(SAL), MAX(SAL) FROM EMP GROUP BY DEPTNO SELECT ENAME, EMPNO FROM EMP WHERE DEPTNO = :dept_number
▩ Method 4
이 방법은 실행 시점까지 SELECT-LIST와 호스트변수의 데이터 타입과 개수, 위치를 모를 경우 사용하는 방법이다.
INSERT INTO EMP () VALUES () SELECT FROM EMP WHERE DEPTNO = 20
위의 네 가지 방법 모두 Dynamic SQL문을 charactor string에 저장하고, "EXEC SQL"과 ";"는 생략한다. Method 2와 3은 입력 호스트변수의 위치와 데이터타입을 프리 컴파일 시점까지 정해져 있어야 한다. Method 4는 가장 유연성이 좋다. 반면에 복잡한 코딩이 들어가도 Dynamic SQL의 개념을 알고 있어야 하기 때문에 잘 사용하지 않는다. 보통 Method 4는 Method 1, 2, 3으로 해결 할 수 없는 경우에 사용한다. 만약 프리 컴파일 옵션을 DBMS=V6나 DBMS=V6_CHAR인 경우 SQL문을 배열에 저장하기 전에 BLANK를 채워줘야 한다. 그렇게 함으로서 변수를 CLEAR해 준다. 특히 배열을 다른 SQL문에서 다시 사용할 경우에는 특히 중요하다. 항상 SQL문을 저장하기 전에 호스트 스트링을 초기화 해야 한다. 오라클에서는 NULL-TERNIMATE는 스트링의 마지막이라고 인식하지 못하고 SQL문의 일부로 인식하기 때문에 사용하여서는 안 된다. 만일 프리 컴파일 옵션을 DBMS=V7로 했을 경우, 스트링의 값은 "PREPARE", 또는 "EXECUTE IMMEDIATE"하기 전에 NULL-TERNIMATE로 끝을 맺어줘야 한다. DBMS옵션의 값을 개의치 않을 경우에는 다이나믹 SQL문을 저장할 변수로 VARCHAR를 쓸 경우, VARCHAR의 length를 "PREPARE"나 "EXECUTE IMMEDIATE"를 실행하기 전에 정확하게 SET해주어야 한다.
▩ Method 5
결과값이 단순히 SUCCESS나 FAILURE인 간단하고 호스트변수를 사용하지 않는 다이나믹 SQL문일 경우 사용한다. 이 방법은 "EXECUTE IMMEDIATE"를 사용하여 다이나믹 SQL문을 실행시킨다.
EXEC SQL EXECUTE IMMEDIATE {:host_string | string_literal }; char sql_stmt[132]; .... for (;;) { printf("Enter SQL statement: "); gets(sql_stmt); if(*sql_stmt == '\0') break; EXEC SQL EXECUTE IMMEDIATE :sql_stmt; } EXEC SQL EXECUTE IMMEDIATE 'REVOKE RESOURCE FROM MILLER';
▩ Method 6
이 방법으로 실행할 경우 2번의 작업을 거쳐야 한다. 다이나믹SQL문은 QUERY문이어서는 안되고 첫 번째로 PREPARE하고 EXECUTE되어 진다. SQL문에는 호스트변수와 INDICATOR변수를 가질 수 있다. PREPARE문은 한번만 수행하면 되고 EXECUTE문은 다른 호스트 변수 값으로 여러 번 수행할 수 있다.. 더 나아가서 COMMIT이나 ROLLBACK문장을 수행하고 나서도 다시 PREPARE할 필요가 없다.(LOG OFF이나 RECONNECT가 아닌 경우)
EXEC SQL PREPARE statement_name FROM {:host_string | :string_literal };
PRAPARE 명령은 SQL문을 PARSING하고 이름을 부여한다. 위에서 statement_name은 호스트변수, 프로그램 변수가 아니고 프리컴파일러가 사용할 임시적인 변수로 DECALRE SECTION에 기술할 필요가 없다.
EXEC SQL EXECUTE statement_name [USING host_variable_list]; :host_variable_list = :host_variable[:indicator1] [, :host_variable[:indicator2], ...]
EXECUTE는 PARSING된 SQL문을 "USING" 절의 호스트변수를 이용하여 수행한다. 실제로 사용되는 모습은 아래와 같다.
... int emp_number; char delete_stmt[120], search_cond[40], temp[10]; ... strcpy(delete_stmt, "DELETE FROM EMP WHERE EMPNO = :n AND "); printf("다음의 SQL문에서 검색조건을 입력하여 완성하시오.\n"); printf("%s\n", delete_stmt); gets(search_cond); strcat(delete_stmt, search_cond); EXEC SQL PREPARE sql_stmt FROM :delete_stmt; for(;;) { printf("사원번호를 입력하세요: "); get(temp); emp_number = atoi(temp); if (emp_number == 0) break; EXEC SQL EXECUTE sql_stmt USING :emp_number; } ......
USING절의 이용 SQL문이 EXECUTE될 때, USING절에 입력된 호스트변수의 값은 PREPARE된 다이나믹 SQL문에서 대응되는 위치에 대치된다. PREPARE된 다이나믹SQL문에서 각각의 변수 위치는 USING절의 각각의 다른 호스트변수와 대치되어야 한다.
▩ Method 7
이 방법은 Method 2와 비슷하나 PREPARE문장과 함께 커서를 선언하고 조작 하는 게 필요하다. QUERY문을 사용할 수 있다. 사실상 다이나믹SQL문이 QUERY문이면 Method3이나 4를 사용해야 한다. SELECT-LIST의 칼럼의 개수와 호스트 변수의 개수가 프리 컴파일 시점에서는 정해져 있어야 한다. 실행시점 전까지는 테이블명과 컬럼명등 데이터베이스 OBJECT의 이름은 정해져야 한다. 데이터베이스 OBJECT의 이름은 호스트변수를 사용하지 못한다. 조건 절(WHERE)이나 GROUP BY절, ORDER BY절 역시 실행 시점까지 정해져야 한다.
EXEC SQL PREPARE statement_name FROM { :host_string | string_literal }; EXEC SQL DECALRE cursor_name CURSOR FOR statement_name; EXEC SQL OPEN cursor_name [USING host_variable_list ]; EXEC SQL FETCH cursor_name INTO host_variable_list; EXEC SQL CLOSE cursor_name;
PREPARE는 다이나믹SQL문을 PARSING하고 이름을 부여한다. 아래의 예제는 select_stmt문자열을 sql_stmt로 이름을 부여한다.
char select_stmt[132] = "SELECT MGR, JOB FROM EMP WHERE SAL < :salary"; EXEC SQL PREPARE sql_stmt FROM :select_stmt;
보통 WHERE절은 실행 시점에서 터미널로부터 입력되거나 어플리케이션 프로그램에서 자동으로 생성되게 한다. DECLARE명령은 PREPARE에 의해 붙여진 이름의 SQL문으로 커서를 정의한다.
EXEC SQL PREPARE sql_stmt FROM :select_stmt EXEC SQL DECLARE emp_cursor CURSOR FOR sql_stmt;
위의 예에서 sql_stmt와 emp_cursor는 호스트변수나 프로그램의 변수로 선언할 필요가 없다. OPEN명령은 커서에 메모리를 할당하고, 입력된 호스트변수를 BIND하고, QUERY를 실행시키고, 실행에 의해 나오는 데이터를 active set으로 설정한다. OPEN명령은 커서를 ACTIVE SET의 첫 번째 ROW에 위치하게 되고, sqlerrd[2]를 0으로 set한다.
EXEC SQL OPEN emp_cursor USING :salary;
FETCH명령은 INTO절에 대응되는 호스트 변수에ACTIVE SET에서 ROW를 리턴 한다. 만약 더 이상의 데이터가 없으면 오라클은 sqlca.sqlcode의 값을 1403을 설정하고 "no data found"를 리턴 한다.
EXEC SQL FETCH emp_cursor INTO :mgr_number, :job_title;
CLOSE명령은 커서를 무효화 시킨다. 커서를 CLOSE하고 난 후에는 더 이상 FETCH는 실행되지 않는다.
EXEC SQL CLOSE emp_cursor;
'DBMS > ORACLE' 카테고리의 다른 글
[ORACLE] DROP TABLE (0) | 2009.11.13 |
---|---|
ORACLE ROWNUM의 동작 원리와 활용 방법 (0) | 2009.10.21 |
ORACLE rownum 설명.. 그리고 hint 절을 사용하자.. 게시판 페이지처리 (0) | 2009.10.20 |