IT기술/Oracle

5 Sub Query (서브 쿼리)

dobbby 2013. 11. 7. 15:25
반응형

1. Sub Query 란?

쿼리 안에 또 다른 쿼리가 담겨 있는 것.

Sub Query 부분은 WHERE 절 연산자 오른쪽에 위치해야 하며 반드시 괄호로 묶어야 한다.

특별한 경우(Top-n 분석 등)를 제외하고는 Sub Query 절에 Order By 절이 올 수 없다.

단일 행 Sub Query와 다중 행 Sub Query에 따라 연산자를 잘 선택해야 한다.


2. Sub Query의 종류

(1) 단일 행 Sub Query

단일 행 서브 쿼리란 서브 쿼리의 결과가 1개의 행만 나오는 것을 말한다.


단일 행 서브 쿼리일 경우 WHERE 절에서 사용되는 연산자

연산자    의미

=            같다

<>         같지 않다

>           크다

>=          크거나 같다

<            작다

<=          작거나 같다


student 테이블과 department 테이블을 사용하여 이윤나 학생과 deptno1이 동일한 학생들의 이름과 1전공 이름을 출력하라.

SELECT    s.name, d.dname

FROM       student s, department d

WHERE     s.deptno1 = d.deptno

AND         s.deptno1 = (

  SELECT     deptno1

  FROM        student

  WHERE      name = '이윤나'

  ) ;


professor 테이블에서 입사일이 송도권 교수보다 나중에 입사한 사람의 이름과 입사일, 학과명을 출력하라.

SELECT    p.name, p.hiredate, d.dname

FROM       professor p, department d

WHERE     p.deptno = d.deptno

AND         p.hiredate > (

   SELECT    hiredate

   FROM       professor

   WHERE     name = '송도권'

    ) ;


student 테이블에서 deptno1이 101번인 학과의 평균 몸무게보다 몸무게가 많은 학생들의 이름과 몸무게를 출력하라.

SELECT    name, weight

FROM       student

WHERE     weight > (

SELECT    AVG(weight)

FROM       student

WHERE     deptno1 = 101

) ;



professor 테이블에서 심슨 교수와 같은 입사일에 입사한 교수 중에서 조인형 교수보다 월급을 적게 받는 교수의 이름과 급여, 입사일을 출력하라.

SELECT    name, pay, hiredate

FROM       professor

WHERE     hiredate = (

SELECT    hiredate

FROM       professor

WHERE     name = '심슨'

)

AND         pay < (

SELECT    pay

FROM       professor

WHERE     name = '조인형'

) ;



(2) 다중 행 Sub Query

다중행 서브 쿼리란 서브 쿼리의 결과가 2건 이상 출력되는 것을 말한다.


다중행 서브 쿼리 연산자

연산자         의미

IN             같은 값을 찾음

>ANY        최소값을 반환

<ANY        최대값을 반환

<ALL         최소값을 반환

>ALL         최대값을 반환


emp2테이블과 dept2 테이블을 참조하여 근무지역이 서울 지사인 모든 사원의 사번과 이름, 부서번호 출력

SELECT    empno, name, deptno

FROM       emp2

WHERE     deptno IN (

SELECT    dcode

FROM       dept2

WHERE     area = '서울지사'

);


emp2 테이블을 사용하여 전체 직원 중 과장 직급의 최소 연봉자보다 연봉이 높은 사람의 이름과 직급, 연봉을 출력. 형식은 천단위 구분기호와 원표시

SELECT    name "이름", position "직급", TO_CHAR(pay, '999,999,999')||' 원' "연봉"

FROM       emp2

WHERE     pay >ANY (

         SELECT    pay

         FROM       emp2

   WHERE     position = '과장'

          ) ;


student 테이블을 조회하여 전체 학생 중에서 체중이 4학년 학생들의 체중에서 가장 적게 나가는 학생보다 적은 학생의 이름과 학년과 몸무게를 출력.

SELECT    name "이름", grade "학년",  weight "몸무게"

FROM       student

WHERE     weight < ALL (

SELECT    weight

FROM       student

WHERE     grade = 4

) ;



(3) 다중 칼럼 Sub Query

다중 칼럼 서브 쿼리란 서브 쿼리의 결과가 여러 칼럼인 경우를 말한다.


student 테이블을 조회하여 각 학년별로 최대 키를 가진 학생들의 학년과 이름과 키를 출력.

SELECT    grade "학년", name, "이름", height "키"

FROM       student

WHERE     (grade, height) IN (

  SELECT    grade, MAX(height)

  FROM       student

  GROUP BY grade

  ) ;


professor 테이블을 조회하여 각 학과별로 입사일이 가장 오래된 교수의 교수번호와 이름, 입사일, 학과명을 출력. 단, 학과명순으로 오름차순 정렬.

SELECT    p.profno "교수번호", p.name "이름", p.hiredate "입사일", d.dname "학과명"

FROM       professor p, department d

WHERE     p.deptno = d.deptno

AND         (p.deptno, p.hiredate) IN (

         SELECT    deptno, MIN(hiredate)

         FROM       professor

         GROUP BY deptno

         )

ORDER BY 4 ;


emp2 테이블을 조회하여 직급별로 해당 직급에서 최대 연봉을 받는 직원의 이름과 직급, 연봉을 출력. 단, 연봉순으로 오름차순 정렬

SELECT    name "이름", position "직급", pay "연봉"

FROM       emp2

WHERE     (position, pay) IN (

   SELECT     position, MAX(pay)

   FROM        emp2

   GROUP BY position

   )

ORDER BY 3 ;


emp2 테이블을 조회하여 각 부서별 평균 연봉을 구하고 그 중에서 평균 연봉이 가장 적은 부서의 평균 연봉보다 적게 받는 직원들의 부서명, 직원명, 연봉을 출력

SELECT    d.dname "부서명", e.name "이름", e.pay "연봉"

FROM       dept2 d, emp2 e

WHERE     d.dcode = e.deptno

AND         e.pay < ALL (

   SELECT     AVG(NVL(pay,0))

   FROM        emp2

   GROUP BY deptno

   ) ; 



(4) 상호 연관 Sub Query

상호 연관 서브 쿼리란 메인 쿼리 값을 서브 쿼리에 주고 서브 쿼리를 수행한 후 

그 결과를 다시 메인 쿼리로 반환해서 수행하는 서브 쿼리를 말한다.

이 서브쿼리는 잘못사용될 경우 성능 저하의 원인이 될 수 있다.


SELECT    name "사원이름", position "직급", pay "급여"

FROM       emp2 a

WHERE     pay >= (

  SELECT    AVG(pay)

  FROM       emp2 b

  WHERE     a.position = b.position

  ) ;


서브 쿼리에 메인쿼리의 테이블이 들어오지 않게 할 것.


* 서브 쿼리에 오는 위치에 따라 그 이름이 다르다.

SELECT    (Sub Query) ← 1행만 반환할 경우 Scalar Sub Query (스칼라 서브쿼리)

FROM       (Sub Query) ← Inline View (인라인 뷰)

WHERE     (Sub Query) ← Sub Query 라고 부른다.



3. Scalar Sub Query (스칼라 서브쿼리)

스칼라 서브쿼리는 SELECT 절에 오는 서브 쿼리로 한번에 결과를 1행씩 반환한다.


SELECT    name "사원이름", (

SELECT     dname

FROM        dept2 d

WHERE      e.deptno = d.dcode

) "부서이름"

FROM      emp2 e ;


스칼라 서브쿼리는 서브쿼리의 결과가 없을 경우 Null을 돌려준다. Outer Join과 동일함. 


스칼라 서브쿼리 동작 원리

1. 메인 쿼리를 수행한 후 스칼라 서브 쿼리에 필요한 값을 제공한다.

2. 스칼라 서브 쿼리를 수행하기 위해 필요한 데이터가 들어있는 블록을 메모리로 로딩한다.

3. 메인 쿼리에서 주어진 조건을 가지고 필요한 값을 찾아 메모리에 입력값과 출력값으로 메모리 내의 query execution cache 라는 곳에 저장해둔다.

    여기서 입력값은 메인 쿼리에서 주어진 값이고 출력값은 스칼라 서브 쿼리를 수행한 후 나온 결과 값이다.

    이 값을 저장하는 캐쉬 값을 지정하는 파라미터는 _query_execution_cache_max_size 이다.

4. 다음 조건이 메인 쿼리에서 스칼라 서브 쿼리로 들어오면 해쉬 함수를 이용해 해당 값이 캐쉬에 존재하는지 찾고 있으면 즉시 결과 값을 출력하고

   없으면 다시 블록을 액세스 해서 해당 값을 찾은 후 다시 메모리에 캐쉬해 둔다.

5. 메인 쿼리가 끝날 때가지 반복한다.


위의 순서에서 알 수 있듯이 스칼라 서브쿼리가 빠른 이유는 찾는 데이터가 메모리에 만들어져 있는 값을 찾아오기 때문이다.

만약 모든 데이터가 메모리에 없거나 또는 데이터 양이 많을 경우는 query execution cache 에서 해당 데이터를 찾는 시간이 더 걸리기 때문에

Join 보다 속도가 더 걸리게 된다. 

그래서 스칼라 서브쿼리는 데이터 종류가 적고 갯수도 비교적 적은 코드성 테이블에서 데이터를 가져올 때 사용하기를 추천하는 것이다.



테스트를 위해 t3, t4 테이블을 생성한 후 데이터를 insert 한다.


CREATE TABLE t3 (

no number,

name varchar2(10),

deptno number);


CREATE TABLE t4 (

deptno number,

dname varchar2(10));


INSERT INTO t3 VALUES(1,'aaa',100);

INSERT INTO t3 VALUES(2,'bbb',200);

INSERT INTO t3 VALUES(3,'ccc',300);

commit;


INSERT INTO t4 VALUES(100,'ddd');

INSERT INTO t4 VALUES(100, 'eee');

INSERT INTO t4 VALUES(200, 'fff');

INSERT INTO t4 VALUES(300, 'ggg');

commit;



1) 스칼라 서브쿼리에서 참조하는 값이 2개 이상일 경우

SELECT    t3.no, t3.name, (

SELECT    dname

FROM       t4

WHERE     t3.deptno = t4.deptno

)

FROM       t3;


에러가 발생하는 이유는  t4 테이블의 100번 dname의 값이 2개가 있어서 오라클이 2개 중 무엇을 가져와야 할지 모르겠다는 의미이다.

에러를 수정하기 위해 중복값이 없도록 하겠다.


SELECT    *

FROM       t4;


UPDATE    t4

SET          deptno = 400

WHERE     dname = 'ddd';


commit;


SELECT    t3.no, t3.name, (

SELECT    dname

FROM       t4

WHERE     t3.deptno = t4.deptno

)

FROM       t3;



2) 스칼라 서브 쿼리에서 2개 이상의 컬럼을 조회할 경우

SELECT    t3.no, t3.name, (

 SELECT    dname, deptno

 FROM       t4

 WHERE     t3.deptno = t4.deptno

 )

FROM       t3;


SQL 오류: ORA-00913: 값의 수가 너무 많습니다

00913. 00000 -  "too many values"


위 테스트에서 볼 수 있듯이 스칼라 서브 쿼리에서 2개 이상의 컬럼은 사용할 수 없다.



스칼라 서브쿼리와, Join 테스트

0. 실습 전에 필요한 파일들의 용량을 증가한다.


conn / as sysdba;

set line 200

set timing on    ← SQL 수행 시간을 표시하도록 설정

col tablespace_name for a10

col mb for 999.99

col file_name for a50


SELECT    tablespace_name, bytes/1024/1024MB, file_name

FROM       dba_data_files;


TABLESPACE      MB FILE_NAME

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

USERS         5.00 /app/oracle/oradata/testdb/users01.dbf

UNDOTBS1     75.00 /app/oracle/oradata/testdb/undotbs01.dbf

SYSAUX      520.00 /app/oracle/oradata/testdb/sysaux01.dbf

SYSTEM      690.00 /app/oracle/oradata/testdb/system01.dbf


Elapsed: 00:00:00.12


ALTER    database datafile '/app/oracle/oradata/testdb/users01.dbf'

autoextend on;



1. 테스트용 테이블과 시퀀스를 생성한 후 데이터를 입력한다.

CREATE TABLE st1 (

no number,

name varchar2(20),

deptno number

)

tablespace users;


CREATE TABLE st2 (

deptno number,

dname varchar2(20)

)

tablespace users;


CREATE SEQUENCE s_st1;


CREATE SEQUENCE s_st2;


BEGIN

for i in 1..5000000 loop

INSERT INTO st1

VALUES (i, dbms_random.string('a',20),s_st1.nextval);

end loop;

commit;

END;

/


BEGIN

for i in 1..5000000 loop

INSERT INTO st2

VALUES (s_st2.nextval, dbms_random.string('a',20));

end loop;

commit;

END;

/


2. st2 테이블의 dname 컬럼에 중복 값이 없는지 확인한다.

SELECT    COUNT(DISTINCT(dname))

FROM       st2;


COUNT(DISTINCT(DNAME))

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

               5000000


없다.


3. 테스트를 위해 두 테이블 모두 조인 조건 컬럼에 인덱스를 생성한다.

CREATE INDEX idx_st1_deptno on st1(deptno);

CREATE INDEX idx_st2_deptno on st2(deptno);


테스트 조건은 deptno 컬럼이 1-290000 인 데이터를 조인해서 st1.no, st1.name, st2.detpno, st2.dname을 출력하는 것으로 하고

아래의 2가지 경우로 테스트를 수행한다.


4. 일반적인 Join을 수행하여 데이터를 조회한다.

set timing on


SELECT    st1.no, st1.name, st2.deptno, st2.dname

FROM       st1, st2

WHERE     st1.deptno = st2.deptno

AND         st1.deptno > 0

AND         st1.deptno < 300000;


299999 rows selected.


Elapsed: 00:01:20.91


5. 스칼라 서브쿼리를 사용하여 데이터를 조회한다.

SELECT    st1.no, st1.name, st1.deptno, (

SELECT    dname

FROM       st2

WHERE     st1.deptno = st2.deptno

)

FROM       st1

WHERE     no > 0 

AND          no < 3000000;


2999999 rows selected.


Elapsed: 00:13:45.93


일반적인 Join방법보다 훨씬 더 오래 걸린다.



실행계획을 보면 st1 테이블은 full scan 으로 읽고 st2 테이블은 index 를 사용한 것을 확인할 수 있다.

데이터가 너무 많아서 인덱스를 읽지 않고 일부러 full scan 을 선택한 것이다.

위 테스트 결과에서 알 수 있듯이 스칼라 서브 쿼리가 일반 Join보다 무조건 성능이 좋은 것은 아니다.


스칼라 서브 쿼리는 일반적으로 데이터의 종류와 양이 적은 코드 성격의 데이터를 조인을 통해 조회를 할 때는 성능이 좋지만 

그런 상황이 아닐 경우는 위의 테스트처럼 일반적인 Join 보다 성능이 더 저하될 수 있다는 것을 꼭 기억해야 한다.



4. WITH 절을 활용한 Sub Query

이 방법은 오라클 9i 버전부터 지원되는 방법으로 WITH 절을 사용하여 원하는 테이블을 메모리에 미리 뷰처럼 가상의 테이블로 생성시킨 후 

데이터를 가져오는 기법이다.

디스크에서 데이터를 읽지 않고 미리 메모리에서 만들어 둔 테이블에서 데이터를 호출하기 때문에 일반 서브 쿼리보다 일반적으로 좋은 성능을 발휘한다.

특히 UNION 에 똑같은 테이블을 조회하는 쿼리가 계속 들어갈 경우 사용하면 한번만 기술하고 반복해서 불러 쓰는 방법을 쓸 수 있어서 문장이 많이 간결해진다.


1) 기본문법


단일 가상 테이블 생성

WITH a AS

(

SELECT QUERY...

)

SELECT    *    FROM       a;


다중 가상테이블 생성

WITH a AS

(

SELECT QUERY...

),

b AS

(

SELECT QUERY...

)

SELECT    *    FROM       a

UNION ALL

SELECT    *    FROM       a;


WITH 절 안에는 SELECT 문장만 쓸 수 있다.

WITH 절 안에 또 다른 WITH 절을 쓸 수 없다.



실습1. 대용량의 테이블을 생성한 후 최대값과 최소값의 차이를 구하기

0.  대용량  테이블을  생성하기  위해  관리자로  로그인  후  필요한  파일들의  용량을  증가시킨다.

1.  테스트용  테이블  (with_test1)  을  생성  후  500  만  건의  데이터를  입력한다.

2.  최대값과  최소값의  차이를  max , min  함수를  이용하여  구할  때  소요  시간  확인한다.

3.  인덱스를  생성  후  최대값과  최소값의  차이를  구하고  소요  시간을  확인한다.

4. with  절을  사용하여  최대값과  최소값의  차이를  구하고  소요시간을  확인한다.


0.  실습  전에  필요한  파일들의  용량을  증가한다. (위에서 작업한 경우 하지 않아도 된다)

conn / as sysdba; 

set line 200 

set timing on

col tablespace_name for a10 

col mb for 999.99 

col file_name for a50 


SELECT    tablespace_name, bytes/1024/1024 MB, file_name

FROM       dba_data_files;


ALTER    database datafile '/app/oracle/oradata/testdb/users01.dbf'

autoextend on;



1. scott 계정으로 로그인 하여 with_test1 테이블 생성 후 데이터 500만 건 입력
conn scott/tiger;

CREATE TABLE with_test1 (
no number,
name varchar2(10),
pay number(6)
)
tablespace users;

BEGIN
for i in 1..5000000 loop
INSERT INTO with_test1
VALUES (i, dbms_random.string('a',5),
dbms_random.value(6,999999));
end loop;
commit;
end;
/



SELECT    *

FROM       with_test1


5000000 rows selected.


Elapsed: 00:18:44.45


2. 일반적인 방법인 max 함수와 min


SELECT    MAX(pay) - MIN(pay)

FROM       with_test1 ;


MAX(PAY)-MIN(PAY)

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

           999993


Elapsed: 00:00:09.28


3. 인덱스를 생성 후 2. 의 작업 반복


CREATE INDEX idx_with_pay ON with_test1(pay);


SELECT    MAX(pay) - MIN(pay)

FROM       with_test1 ;


MAX(PAY)-MIN(PAY)

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

           999993


Elapsed: 00:00:00.70


4. With 절을 사용하여 동일 작업 수행


WITH a AS (

/* 최대값을 구하는 쿼리 */

SELECT    /*+ index_desc(w idx_with_pay) */ pay

FROM       with_test1 w

WHERE     pay > 0

AND         rownum = 1),

b AS

/* 최소값을 구하는 쿼리 */

SELECT    /*+ index(w idx_with_pay) */ pay

FROM       with_test1 w

WHERE     pay > 0

AND         rownum = 1)

SELECT    a.pay - b.pay

FROM       a, b;


A.PAY-B.PAY

-----------

     999993


Elapsed: 00:00:00.00




실습  2.  위에서  생성한  with_test1  테이블에서  no  가  120000  번에서  130000  사이인  사람들  중 

가장  pay  가  작은  사람을  찾은  후  그  사람보다  pay  가  작은  사람수를  세는  작업


이  SQL  을  하기  위해  먼저  with_test1  테이블의  no  컬럼에  인덱스를  생성한다.


CREATE INDEX idx_with_no ON with_test1(no); 


Index created.


Elapsed: 00:00:19.83


Step 1.  일반적인  sub query  를  사용하여  데이터를  조회하고  시간 측정

SELECT    count(*) 

FROM       with_test1 

WHERE     pay <all (

    SELECT    /*+ index (w idx_with_n o) */ pay 

    FROM       with_test1 w 

    WHERE     no BETWEEN 120000 AND 130000

    ) ; 


  COUNT(*)

----------

        57


Elapsed: 00:00:05.94


Step 2.  동일한  작업을  with  절을  사용하여  수행한  후  비교  해  보겠습니다.

WITH T AS    

SELECT    /*+ index (w idx_with_pay ) */ min(pay) min_pay 

FROM      with_test1 w 

WHERE    pay >0 

AND        no BETWEEN 120000 AND 130000 

AND        rownum = 1

SELECT    COUNT(*) 

FROM       with_test1 w , t 

WHERE       w.pay < t.min_pay ; 


  COUNT(*)

----------

        57


Elapsed: 00:00:00.00



위의 테스트로 알 수 있듯이 서브 쿼리에서 대량의 데이터를 대상으로 작업을 하여 시간이 오래 걸리는 경우

with 절로 미리 가상의 뷰처럼 작업을 단축 시킬 수 있는 테이블을 생성한 후 업을 수행하면 훨씬 더 빠르고 효과적으로 작업할 수 있다.



=============================여기부터 실습할 것===================================



실습 3: 500만 건의 데이터가 들어있는 테이블에 조인을 사용하여 데이터를 조회


Case1 일반적인 Join을 사용하여 데이터를 조회하기

Case2 with 구문을 사용하여 임시 테이블 생성 후 Join 하기


실습을 위해 아래와 같이 테이블을 생성하고 데이터를 입력한다.


CREATE TABLE t1 (

no number(6),

name varchar2(10),

deptno number(6))

tablespace users ;


CREATE TABLE t2 (

no number(6),

name varchar2(10),

deptno number(6))

tablespace users ;


BEGIN

FOR i IN 1..5000000 LOOP

INSERT INTO t1

VALUES (dbms_random.value(6,999999), 

dbms_random.string('A',9), 

dbms_random.value(6,999999)

) ; 

END LOOP ;

COMMIT ;

END ;


SELECT    COUNT(*)

FROM       t1 ;


BEGIN

FOR i IN 1..5000000 LOOP

INSERT INTO t2 

VALUES (

dbms_random.value(6,999999), 

      dbms_random.string('A',9), 

      dbms_random.value(6,999999)

      ) ; 

END LOOP ;

COMMIT ;

END ;

/


SELECT    COUNT(*)

FROM       t2 ;


join  이  될  조건  컬럼에  아래와  같이  인덱스도  생성한다


CREATE INDEX idx_t1_deptno ON t1(deptno) ; 

CREATE INDEX idx_t2_deptno ON t2(deptno); 



Case 1:  일반적인  join  을  사용하여  데이터를  조회하기 


SELECT    t1.no, t1.name, t2.deptno

FROM       t1 , t2 

WHERE     t1.deptno = t2.deptno

AND         t1.no < 10000 ; 


249644 rows selected.

Elapsed: 00:01:13.99



Case 2: with  구문을  사용하여  임시  테이블  생성  후  join  하기 

WITH a AS    ( 

SELECT    no , name , deptno 

FROM       t1 

WHERE     no < 10000

)

SELECT    a.no , a.name , a.deptno 

FROM       t1 a , t2 b 

WHERE     a.deptno = b.deptno ; 


247474 rows selected. 

Elapsed: 00:00:07.40 


위  결과를  보면  Case  1의  경우와  거의  비슷한  시간이  나온다.  

즉  with  절을  사용해도  크게  변화가  없다는  것이다. 왜  그럴까?  

그  이유는  위  SQL  의  특징을  이해하면  알  수  있다. 

위  SQL  을  수행하면  with  절에  적혀  있는  대로  먼저  t1  테이블에서  no  가  10000  이하인  데이터만  가져와서  메모리상에  임시테이블을  생성한다. 

그  대상  건  수는  아래와  같이  500  만  건  중에서  49558  건이다  (1% 정도이다.) 

조인  대상이  500  만  건에서  약  5만  건으로  1/100  으로  확  줄었다.


SELECT    COUNT(*)

FROM       t1 

WHERE     no <10000 ; 


  COUNT(*) 

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

     49558 

1 row selected. 

Elapsed: 00:00:00.23 


그러나  t2  테이블의  범위는  줄이지  못했다.  

왜냐하면  t1  쪽에서  제공되는  값이  t2  테이블의  어디에  있는지  모르기  때문이다. 

즉  t2  테이블을  전부  다  읽어서  t1  에서  제공되는  값을  찾아야  한다는  것이다. 

위의  경우  인덱스가  있었지만  대상  데이터가  너무  많아서  오라클은  index  를  사용하지 않고,

전체를  다  읽는  full scan  방식을  선택하고  Hash join  방식을  선택했다.


아래의  실행계획을  보면  이  사실을  알  수  있습니다. 

이  테스트로  확인  할  수  있는  것은  with  절을  사용할  경우  효과를  볼  수  있는  것은  

with  절에서 작업  범위를  줄일  수  있는  경우에만  효과가  있다라는  것이다. 

인덱스를  강제로  사용하도록  힌트를  주고  동일하게  실행해서  확인해  보겠다. 


WITH a AS    ( 

SELECT    no , name , deptno 

FROM       t1 

WHERE     no < 10000

SELECT    /*+ index(a idx_t1_deptno) index (b idx_t2_deptno) */ 

   a.no , a.name , a.deptno , b.dname 

FROM      a , t2 b 

WHERE    a.deptno = b.deptno ; 


(중간  결과  생략) 

       NO    NAME         DEPTNO   DNAME 

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

      9900   WFvvpzvPz      999956   xJOimIejQ 

      9900   WFvvpzvPz      999956   MevBJexRC 

      9900   WFvvpzvPz      999956   HBoijppyC 

      9900   WFvvpzvPz      999956   QZppvDVaO 

      9900   WFvvpzvPz      999956   eVtBOtmWh 

      5189   EpLqpvfpZ      999975   NMqtMpNbC 

      5189   EpLqpvfpZ      999975   dWlGPZALY 


247474 rows selected. 

Elapsed: 00:00:49.87   <-  시간이  훨씬  더  오래  걸린  것이  확인된다. 


위  실행계획에서  인덱스를  모두  사용했다. 

위의  여러  가지  테스트  결과로  알  수  있는  것은  Sub  query  절에서  복잡하고  많은  데이터를  처리해야  하는  경우에는 

with  절을  사용하여  미리  원하는  데이터를  메모리  상에  가상의  테이블처럼 만들어  놓은  후  작업을  하면  아주  성능  향상에  좋은  효과가  있다는  것이다. 


그리고  Join  과  같은  경우에는  선행  테이블과  후행  테이블  모두를  with  절로  범위를  줄일  수  있을  경우에는 효과가 있지만

그렇지  않고  두  테이블  중  한가지만  범위를  줄일  경우  성능  향상에 큰  도움이  안  된다는  사실을  확인  했다.

Sub  Query  가  많은  데이터를  처리해서  결과를  추출해야  할  경우  with  절을  적극  사용하시길  권장한다.


**  참고  ** 

위에서  나온  실행계획이란  쿼리를  수행하는  서버  프로세스가  쿼리를  수행하는  순서나  방법을  적어  놓은  것을  뜻한다.  

옵티마이져가  실행계획을  생성해  주는데  이  실행  계획에  따라  쿼리의  수행  속도가  결정된다. 

간단하게  예를  들면  서울에서  부산까지  홍길동이  운전을  해서  가야  하는데  길을  몰라서  가는  방법을  네비게이션에게  물어본다.  

그럼  네비게이션이  길을  알려주는데 어떤  길을  알려주느냐에  따라  부산에  도착하는  시간이  달라지게  된다.  

여기서  운전을  하는  홍길동이  쿼리를  수행하는  서버  프로세스이고  길을  알려주는  네비게이션이 옵티마이져다.  

그리고  네비게이션이  만들어준  길이  실행계획인  것이다.  이  부분에  대한  자세한  것은  튜닝  관련  서적을  참고할것 .




[출처] 다양한 예제로 쉽게 배우는 오라클 SQL과 PL/SQL 서진수 저



반응형