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;
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 서진수 저