'SubQuery'에 해당되는 글 1건
- 서브쿼리 subquery 설명 2010/02/15
서브쿼리란 단독적으로 수행되는 SQL이 아니라 다른 SQL 내에 존재하여 그 SQL이 수행되기 위해 필요한 부수적인 데이터 집합을 제공하기 위한 쿼리이다. 서브쿼리는 메인SQL의 조건체크를 위해서 사용되거나 또는 메인SQL에 데이터 집합을 제공하기 위해서 사용된다.
서브쿼리의 수행결과는 결국 집합의 형태를 띄게 되므로 집합에 대해 비교할 수 있는 =, !=, >, >=, <, <=, IN, NOT IN, EXISTS, NOT EXISTS 연산자 등이 조건체크를 위해서 사용될 수 있으며, 서브쿼리의 집합 앞에 ALL, ANY, SOME 등의 연산을 가할 수 있다.
서브쿼리의 역할
1. SELECT에서의 서브쿼리
결과 데이터 집합을 얻기 위해서 두 개 이상의 테이블에서 데이터를 사용해야 하는 경우, 한 테이블의 데이터에서만 결과를 추출하고, 나머지 테이블은 단지 조건체크를 위해서만 사용되는 경우에 사용될 수 있다. 조인을 대체하기 위해 사용되지만 조인에서와 마찬가지로 액세스 경로에는 주의해야 한다.
예, 총무부에 근무하는 직원들의 급여평균을 구하라.
SELECT AVG(sal)
FROM emp
WHERE deptno = (SELECT deptno FROM dept WHERE dname = ‘총무부’);
2. UPDATE에서의 서브쿼리
UPDATE하고자 하는 대상을 골라내는 조건으로 서브쿼리가 사용될 수 있다.
예. 개발부 직원들의 임금을 10% 인상하라.
UPDATE emp
SET sal = sal * 1.1
WHERE deptno = (SELECT deptno FROM dept WHERE dname = ‘개발부’);
UPDATE하고자 하는 소스 데이터를 추출하기 위해 서브쿼리가 사용될 수 있다.
예. 개발부 직원들의 연봉을 현재 연봉등급의 상한선으로 인상하라.
UPDATE emp e
SET sal = (SELECT hisal FROM salgrade WHERE e.sal BETWEEN losal AND hisal)
WHERE deptno = (SELECT deptno FROM dept WHERE dname = ‘개발부’);
3. DELETE에서의 서브쿼리
DELETE하고자 하는 대상을 골라내는 조건으로 서브쿼리가 사용될 수 있다.
예, A부서 직원들을 해고하라.
DELETE FROM emp
WHERE deptno = (SELECT deptno FROM dept WHERE dname = ‘A부서’);
4. INSERT에서의 서브쿼리
INSERT하고자 하는 소스 데이터를 추출하기 위해 서브쿼리가 사용될 수 있다.
예, 영업부서 직원들의 봉급의 10%와 커미션의 100%를 보너스로 지급하라.
INSERT INTO bonus
SELECT ename, job, sal*0.1, comm
FROM emp
WHERE deptno = (SELECT deptno FROM dept WHERE dname=’영업부’);
5. CREATE TABLE에서의 서브쿼리
테이블 생성 후에 데이터를 생성하기 위한 소스 데이터를 추출하기 위해 서브쿼리가 사용될 수 있다.
팁. 서브쿼리의 결과로 리턴되는 컬럼들의 이름과 데이터타입을 이용하여 테이블이 생성되므로 특정 테이블과 같은 구조를 가지면서 데이터는 없는 테이블을 만드는 경우에 간단하게 사용될 수 있다.
CREATE TABLE temp_tab
AS SELECT * FROM tab WHERE 1 = 0;
SELECT에서 조인과의 차이점
두개 이상의 테이블의 데이터가 필요한 SELECT 문장에서는 조인을 사용해야 하는지, 서브쿼리를 사용해야 하는지에 대한 판단을 내리는데 도움이 되는 결정사항에 대해서 논한다.
1. 서브쿼리를 적용할 수 있는 경우와 없는 경우
메인쿼리는 서브쿼리 쪽에서 데이터를 받아서 결과집합을 만들어 낼 수는 없다. 단지 조건을 검사하는 목적으로만 사용할 뿐이다. 따라서, 서브쿼리에서 액세스하는 테이블의 값이 메인쿼리의 SELECT-LIST에 나오지 않으면 이때에는 서브쿼리를 사용할 수 있지만, SELECT-LIST에 나오면 서브쿼리를 사용할 수 없다.
예. 서울지역 부서에 근무하는 직원의 평균연봉을 구하라.
SELECT AVG(sal)
FROM emp
WHERE deptno IN (SELECT deptno FROM dept WHERE loc = ‘서울’);
이 경우에는 SELECT-LIST는 emp테이블의 sal만 있으면 되고, dept 테이블로부터 유도되는 SELECT-LIST는 없기 때문에 서브쿼리를 사용할 수 있다.
예. 서울지역 부서에 근무하는 직원의 부서별 평균연봉을 구하라.
SELECT d.dname, AVG(e.sal)
FROM dept d, emp e
WHERE d.loc = ‘서울’
AND d.deptno = e.deptno;
이 경우에는 SELECT-LIST를 위해서 dept 테이블의 dname과 emp 테이블의 sal 이 필요하므로 서브쿼리로서는 해결할 수 없고, 반드시 조인을 사용해야 한다.
2. 일대다 관계에서 서브쿼리가 효과적인 경우
테이블 간의 관계에서 일대다(one-to-many)인 경우 일(one)쪽의 테이블에서 SELECT-LIST가 나오고 다(many)쪽의 테이블이 조건체크를 위해서 사용되는 경우 서브쿼리가 효과적으로 사용될 수 있다.
예. 연봉이 3000이상인 직원이 하나라도 존재하는 부서명을 구하라.
SELECT dname
FROM dept d
WHERE EXISTS (SELECT empno FROM emp WHERE deptno = d.deptno and sal > 3000);
조인의 경우
SELECT distinct d.dname
FROM dept d, emp e
WHERE d.deptno = e.deptno
AND e.sal > 3000;
조인으로 수행되는 경우에는 일대다 관계의 테이블을 조인을 한 결과는 다(many)만큼 펼쳐지므로, 조인한 이후에 원하는 결과 집합을 만들기 위해서 distinct 수행한다. 따라서 조인으로 펼쳤다가 distinct로 다시 접는 비효율적인 수행을 하게 된다. 특히 다쪽 집합이 매우 크고 계속해서 증가하는 히스토리성 집합인 경우에는 매우 비효율적으로 수행된다. 반면, 서브쿼리는 다쪽 집합에서 조건검사만 하므로 한 건이라도 조건체크가 만족되면 거기에서 검사가 중지되므로 일대일 관계의 조인과 유사한 효율적인 수행을 하게 된다.
3. 다대일 관계에서 서브쿼리가 효과적인 경우
테이블 관계가 다대일이면서, 다쪽에서 SELECT-LIST가 나오고, 일쪽에서 조건체크가 이루어진다 하더라도, 서브쿼리가 조인에 비해 효과적일 수 있다.
예. 직급이 매니저이면서 서울지역 부서에 근무하는 직원명단을 구하라.
SELECT empno, ename
FROM emp e
WHERE job = ‘MANAGER’
AND EXISTS (SELECT * FROM dept d WHERE deptno = e.deptno and loc=’서울’);
위의 예에서는 emp테이블에서 job=’MANAGER’ 조건을 만족하는 중간집합에서 한 건씩 순차적으로 EXISTS 서브쿼리를 수행해서 true인지 false인지를 결정하는데, 이전 건에서 수행된 결과는 메모리에 캐슁되어 있어서 다음 건에 수행될 때에 만약 같은 e.deptno를 가진다면 서브쿼리를 수행하지 않고서도 true인지 false인지를 결정할 수 있다. 조인의 경우는 이러한 캐슁기능이 없고 데이터 연결을 위해서는 항상 dept 테이블을 찾아야 하므로, 조인보다 서브쿼리가 효과적일 수 있다.
서브쿼리를 이용한 UPDATE
UPDATE 문장에서 서브쿼리를 통해서 데이터소스를 만들어낼 때, 서브쿼리의 실행결과 NOT FOUND가 발생하는 경우, UPDATE하고자 하는 컬럼은 NULL로 UPDATE된다.
예. 부서의 MANAGER의 직원번호를 UPDATE하라.
UPDATE dept d
SET deptmgr = (SELECT empno FROM emp e
WHERE e.deptno = d.deptno AND e.job = ‘MANAGER’);
주의. Outer-Join으로 하더라도 조인이 아니므로 NOT FOUND를 피할 수 없다.
UPDATE dept d
SET deptmgr = (SELECT empno FROM emp e
WHERE e.deptno(+) = d.deptno AND e.job = ‘MANAGER’);
해결. 서브쿼리에서 NOT FOUND가 발생하는 경우 아예 UPDATE를 방지하려면 WHERE 조건에서 EXISTS 서브쿼리를 사용하여 UPDATE 대상에서 제외해야 한다.
UPDATE dept d
SET deptmgr = (SELECT empno FROM emp e
WHERE e.deptno(+) = d.deptno AND e.job = ‘MANAGER’);
WHERE EXISTS (SELECT empno FROM emp
WHERE e.deptno = d.deptno AND e.job = ‘MANAGER’);
서브쿼리가 Aggregate 함수를 이용하는 것이라면 NOT FOUND가 발생하지 않고 NULL이 리턴되므로 NVL 함수를 사용하여 원하는 값으로 UPDATE할 수 있다.
예. 부서별 연봉의 합계를 UPDATE하라.
UPDATE dept d
SET salsum = (SELECT NVL(SUM(sal),0) FROM emp
WHERE deptno = d.deptno);
Self 조인과 마찬가지로 자기 자신의 테이블을 다시 서브쿼리로 찾을 수 있다.
예. 전 사원의 연봉 순위를 매겨라.
UPDATE emp e
SET salrank = (SELECT COUNT(empno) + 1 FROM emp
WHERE sal > e.sal);
이때, 1위의 경우 서브쿼리 수행결과가 NOT FOUND이지만 COUNT(empno)의 결과는 0(zero)이 되므로 NVL 함수를 사용할 필요는 없다.
순위 매기는 서브쿼리에서 만약 특정 조건을 만족하는 집합내의 순위를 매기고 싶다면 서브쿼리에 그 조건을 추가하면 된다.
예. 부서별로 연봉 순위 매겨라.
UPDATE emp e
SET salrank = (SELECT COUNT(empno) + 1 FROM emp
WHERE deptno = e.deptno AND sal > e.sal);
서브쿼리의 SQL 튜닝
서브쿼리의 실행계획은 메인쿼리와의 연결 컬럼의 인덱스 유무, UNIQUE 여부, NOT NULL 여부 등에 따라 다양한 플랜이 존재한다. 서브쿼리가 실행되어지는 플랜에는 상수값 제공, FILTER, JOIN, SEMI-JOIN, ANTI-JOIN 등의 방법이 존재하는데, 하나의 SQL에 대한 플랜은 복수개가 발생할 수 있으므로 항상 개발자는 그 중에서 가장 효율적인 플랜으로 유도할 수 있어야 한다.
단순화 시켜서 생각하면 메인쿼리의 테이블이 먼저 액세스되는냐 아니면 서브쿼리의 테이블이 먼저 액세스되느냐로 구분되어 질 수 있으므로 서브쿼리의 SQL 튜닝은 어느 테이블을 먼저 액세스해야 그 중간결과집합이 작아서 더 효율적으로 수행될 것인가에 초점을 맞추어야 한다.
어떻게 액세스 되고 어떻게 서브쿼리가 수행되는게 좋을지 정했다면 이제 옵티마이저의 플랜을 원하는대로 유도하기 위해서는 인덱스를 생성하거나 힌트를 사용하거나 특정 조건을 추가하는 방식을 사용하여 유도할 수 있다.
1. Single Row & Non corelated Subquery
Single Row Subquery (서브쿼리의 수행결과가 한 건인 서브쿼리) 이면서 Non-corelated Subquery (메인쿼리와 서브쿼리가 연결되는 조건이 명시적으로 표시되지 않은 서브쿼리) 인 경우에 서브쿼리 테이블이 먼저 액세스되는 것이 좋다. 왜냐하면 서브쿼리의 수행결과는 한 건이므로 이 값을 상수값으로 제공받아서 메인쿼리가 풀리는 것이 좋기 때문이다.
SELECT empno, ename
FROM emp e
WHERE deptno = (SELECT deptno FROM dept WHERE dname = ‘영업부’);
SELECT STATEMENT Optimizer=CHOOSE
TABLE ACCESS (BY INDEX ROWID) OF EMP
INDEX (RANGE SCAN) OF EMP_DEPTNO_IX (NON-UNIQUE)
TABLE ACCESS (FULL) OF DEPT
그러나 여기에는 emp.deptno에 인덱스가 있다는 전제가 있다. 만약 emp.deptno에 인덱스가 없다면 상수값 제공이 아닌 FILTER로 플랜이 작성되었을 것이다.
SELECT STATEMENT Optimizer=CHOOSE
FILTER
TABLE ACCESS (FULL) OF EMP
TABLE ACCESS (FULL) OF DEPT
여기에서는 EMP테이블과 DEPT테이블을 각각 FULL SCAN하므로 좋지 않은 플랜이다. 따라서 위의 서브쿼리는 반드시 메인쿼리의 서브쿼리 연결 컬럼에 인덱스를 생성함으로써 상수값 제공으로 플랜을 유도하는 것이 좋다.
복수개의 조건이 각각 Single Row Subquery로부터 값을 받을 경우에도 결합인덱스가 사용조건에 적합하다면 서브쿼리가 먼저 액세스되면서 값을 넘겨줄 수 있다.
예. 오늘
SELECT
FROM sale
WHERE saledate = TO_CHAR(SYSDATE,’yyyymmdd’)
AND custno = (SELECT custno FROM customer WHERE cust_name=’
AND prodcd = (SELECT prodcd FROM product WHERE prod_name = ‘TV’);
SELECT STATEMENT Optimizer=CHOOSE
TABLE ACCESS (BY INDEX ROWID) OF
INDEX (UNIQUE SCAN) OF SALE_PK (UNIQUE)
TABLE ACCESS (FULL) OF CUSTOMER
TABLE ACCESS (FULL) OF PRODUCT
2. Single Row & Correlated Subquery
SELECT *
FROM sale s
WHERE saledate LIKE TO_CHAR(SYSDATE,’yyyymm’)||’%’
AND saledate = (SELECT MAX(saledate) FROM sale s2 WHERE s2.empno = s.empno);
메인쿼리가 먼저 액세스되고 그 결과집합의 각 건마다 서브쿼리의 조건이 체크되는 경우를 말한다. 메인쿼리의 WHERE절에 서브쿼리 이외의 조건이 액세스 경로가 좋고 그 결과집합이 작은 경우이거나 서브쿼리가 메인쿼리와 연결되는 컬럼의 액세스 경로가 좋은 경우에 FILTER를 이용한 실행계획이 세워진다.
서브쿼리의 자체조건이 좋은 액세스 경로를 가지고 메인쿼리가 서브쿼리로부터 넘겨받는 컬럼의 액세스 경로가 좋다면, 서브쿼리가 먼저 수행되어 메인쿼리로 넘어가는 실행계획을 가진다.
SELECT empno, ename
FROM emp e
WHERE deptno = (SELECT deptno FROM dept WHERE dname = ‘영업부’);
SELECT STATEMENT Optimizer=CHOOSE
FILTER
TABLE ACCESS (FULL) OF EMP
TABLE ACCESS (FULL) OF DEPT
여기에서는 EMP테이블을 액세스한 결과가 1000건이라 하면, DEPT테이블을 1000번 조건검사를 수행하게 되는데 TABLE ACCESS (FULL)로 수행하게 되므로 비효율적이다. 이때, 조건검사를 최적화하기 위해서 dept.dname 컬럼에 인덱스 만드는 것을 고려해볼 수 있다.
SELECT STATEMENT Optimizer=CHOOSE
FILTER
TABLE ACCESS (FULL) OF EMP
TABLE ACCESS (BY INDEX ROWID) OF DEPT
INDEX (RANGE SCAN) OF DEPT_DNAME_IX (NON-UNIQUE)
그러나 좀더 근본적으로 생각해보면 서브쿼리가 먼저 수행되었다면 dept 테이블을 액세스한 결과는 1건이었을 것이고 그 값을 이용해서 emp 테이블을 액세스하면 효율적이지만 emp.deptno를 인덱스로 액세스할 수 없었기 때문에 FILTER로 수행된 것이다. 이때에는 emp.deptno에 인덱스를 추가하는 것을 우선적으로 고려해야 한다.
SELECT STATEMENT Optimizer=CHOOSE
TABLE ACCESS (BY INDEX ROWID) OF EMP
INDEX (RANGE SCAN) OF EMP_DEPTNO_IX (NON-UNIQUE)
TABLE ACCESS (BY INDEX ROWID) OF DEPT
INDEX (RANGE SCAN) OF DEPT_DNAME_IX (NON-UNIQUE)
다대일 서브쿼리가 조인보다 효과적인 예에서 나왔던 SQL을 다시 살펴보면 FILTER를 더욱 효과적으로 수행하기 위해서 인덱스를 구성할 필요가 있음을 알게 된다.
SELECT empno, ename
FROM emp e
WHERE job = ‘MANAGER’
AND EXISTS (SELECT * FROM dept d WHERE deptno = e.deptno and loc=’서울’);
인덱스가 없는 경우의 플랜
SELECT STATEMENT Optimizer=CHOOSE
FILTER
TABLE ACCESS (FULL) OF EMP
TABLE ACCESS (BY INDEX ROWID) OF DEPT
INDEX (UNIQUE SCAN) OF DEPT_PK (UNIQUE)
인덱스가 있는 경우의 플랜
SELECT STATEMENT Optimizer=CHOOSE
FILTER
TABLE ACCESS (BY INDEX ROWID) OF EMP
INDEX (RANGE SCAN) OF EMP_JOB_DEPTNO_IX (NON-UNIQUE)
TABLE ACCESS (BY INDEX ROWID) OF DEPT
INDEX (UNIQUE SCAN) OF PK_DEPT (UNIQUE)
이 경우에는 emp 테이블을 job + deptno로 구성된 인덱스를 사용하므로 job=’MANAGER’로 걸러진 중간결과집합을 deptno 순서대로 서브쿼리를 검사하는데, deptno값을 이용하여 검사하므로 FILTER의 버퍼기능을 극대화할 수 있다. 특히 대용량 데이터베이스 경우에는 이러한 기법이 필요하다.
3. JOIN으로 수행되는 서브쿼리
서브쿼리의 수행결과가 1건 이상인 경우에는 =이 아닌 IN으로 비교하게 되는데, IN을 사용한 서브쿼리는 항상 조인으로 수행된다. 이때, 메인쿼리와 서브쿼리의 연결 컬럼이 인덱스가 있느냐 없느냐 여부에 따라서 메인쿼리와 서브쿼리의 먼저 수행되는 쿼리가 결정된다. 예를 들어, 메인쿼리의 연결 컬럼이 인덱스가 있는 경우 서브쿼리가 먼저 수행되고, 서브쿼리의 연결 컬럼이 인덱스가 있는 경우 메인쿼리가 먼저 수행된다. 두 쿼리 모두 인덱스를 사용하지 못한다면 Nested-Loops 조인을 사용하지 못하고, Sort-Merge 조인이나 Hash 조인을 사용하게 된다.
SELECT empno, ename
FROM emp e
WHERE deptno IN (SELECT deptno FROM dept WHERE loc = ‘서울’);
지금 dept.deptno에 인덱스가 있으므로 메인쿼리가 먼저 수행될 것이다.
SELECT STATEMENT Optimizer=CHOOSE
NESTED LOOPS
TABLE ACCESS (FULL) OF EMP
TABLE ACCESS (BY INDEX ROWID) OF DEPT
INDEX (UNIQUE SCAN) OF PK_DEPT (UNIQUE)
만약, emp.deptno에 인덱스가 있다면 서브쿼리가 먼저 수행될 것이다.
SELECT STATEMENT Optimizer=CHOOSE
NESTED LOOPS
VIEW
SORT (UNIQUE)
TABLE ACCESS (FULL) OF DEPT
TABLE ACCESS (BY INDEX ROWID) OF EMP
INDEX (RANGE SCAN) OF EMP_DEPTNO_IX (NON-UNIQUE)
조인으로 수행되는 서브쿼리가 메인쿼리보다 먼저 수행되면 서브쿼리의 중간결과집합은 UNIQUE를 보장하기 위해서 SORT를 수행한다. 그 이유는 일대다 관계에서의 조인은 항상 다쪽의 개수만큼 결과가 나오므로 서브쿼리 결과가 혹시 다쪽일지도 모른다는 우려 때문이다.
그러나 서브쿼리를 수행하기 위해서 TABLE ACCESS (FULL)을 수행하지 않고 인덱스를 사용한다면, 그리고 메인쿼리와의 연결 컬럼이 Primary Key 컬럼이라면 UNIQUE하다는 것을 옵티마이저가 알고 있으므로 SORT(UNIQUE)를 수행하지 않는다.
SELECT STATEMENT Optimizer=CHOOSE
NESTED LOOPS
TABLE ACCESS (BY INDEX ROWID) OF DEPT
INDEX (RANGE SCAN) OF DEPT_LOC_IX (NON-UNIQUE)
TABLE ACCESS (BY INDEX ROWID) OF EMP
INDEX (RANGE SCAN) OF EMP_DEPTNO_IX (NON-UNIQUE)
만약, dept.deptno와 emp.deptno가 동시에 인덱스가 없는 경우라면, Nested-Loops 조인으로 수행되지 못한다.
SELECT STATEMENT Optimizer=CHOOSE
MERGE JOIN
SORT (JOIN)
TABLE ACCESS (FULL) OF EMP
SORT (JOIN)
VIEW
SORT (UNIQUE)
TABLE ACCESS (BY INDEX ROWID) OF DEPT
INDEX (RANGE SCAN) OF DEPT_LOC_IX (NON-UNIQUE)
4. SEMI-JOIN으로 수행되는 서브쿼리
예. 커미션이 500이상인 사원이 하나라도 존재하는 부서의 명단을 구하라.
SELECT deptno, dname
FROM dept d
WHERE EXISTS (SELECT * FROM emp WHERE deptno = d.deptno AND comm > 500);
SEMI-JOIN으로 수행되지 않는다면 FILTER로 수행된다.
SELECT STATEMENT Optimizer=CHOOSE
FILTER
TABLE ACCESS (FULL) OF DEPT
TABLE ACCESS (BY INDEX ROWID) OF EMP
INDEX (RANGE SCAN) OF EMP_DEPTNO_IX (NON-UNIQUE)
5. ANTI-JOIN으로 수행되는 서브쿼리
부정형 서브쿼리를 수행하기 위해 NOT IN 또는 NOT EXISTS를 사용한 경우에는 ANTI-JOIN으로 수행된다.
예. 서울지역에 근무하지 않는 사원의 명단을 구하라.
SELECT empno, ename
FROM emp e
WHERE deptno NOT IN (SELECT deptno FROM dept WHERE loc = ‘서울’);
ANTI-JOIN은 Nested-Loops 조인으로 수행된다. 만약 Hash나 Sort-Merge 조인으로 변경하기 위해서는 서브쿼리 문장내에서 HASH_AJ 또는 MERGE_AJ의 힌트를 사용하거나 ALWAYS_ANTI_JOIN의 파라메터를 HASH 또는 MERGE로 세팅한다. 단, ANTI-JOIN을 Sort-Merge 또는 Hash 조인으로 수행하기 위해서는 연결컬럼 모두 NOT NULL이라는 보장이 있어야 한다. 따라서 NOT NULL 컬럼이 아니라면 IS NOT NULL 조건을 추가해주어야 한다. 아래의 SQL에서 dept.deptno는 Primary Key이므로 이미 NOT NULL 컬럼이 확실하고, emp.deptno에 대해서 IS NOT NULL 조건을 추가한다.
SELECT empno, ename
FROM emp e
WHERE deptno NOT IN (SELECT /*+ MERGE_AJ */ deptno FROM dept WHERE loc = ‘서울’)
AND deptno IS NOT NULL;
SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1.98 Bytes=116.82)
MERGE JOIN (ANTI) (Cost=6 Card=1.98 Bytes=116.82)
SORT (JOIN) (Cost=3 Card=2 Bytes=92)
TABLE ACCESS (FULL) OF EMP (Cost=1 Card=2 Bytes=92)
SORT (UNIQUE) (Cost=3 Card=1 Bytes=13)
VIEW (Cost=1 Card=1 Bytes=13)
TABLE ACCESS (BY INDEX ROWID) OF DEPT (Cost=1 Card=1 Bytes=21)
INDEX (RANGE SCAN) OF DEPT_LOC_IX (NON-UNIQUE) (Cost=1 Card=1)
SELECT empno, ename
FROM emp e
WHERE deptno NOT IN (SELECT /*+ HASH_AJ */ deptno FROM dept WHERE loc = ‘서울’)
AND deptno IS NOT NULL;
SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=20.79 Bytes=956.34)
HASH JOIN (ANTI) (Cost=3 Card=20.79 Bytes=956.34)
TABLE ACCESS (FULL) OF EMP (Cost=1 Card=21 Bytes=693)
VIEW (Cost=1 Card=1 Bytes=13)
TABLE ACCESS (BY INDEX ROWID) OF DEPT (Cost=1 Card=1 Bytes=21)
INDEX (RANGE SCAN) OF DEPT_LOC_IX (NON-UNIQUE) (Cost=1 Card=1)
ANTI-JOIN으로 수행되지 않는다면 FILTER로 수행된다.
SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=2 Bytes=66)
FILTER
TABLE ACCESS (FULL) OF EMP (Cost=1 Card=2 Bytes=66)
TABLE ACCESS (BY INDEX ROWID) OF DEPT (Cost=1 Card=1 Bytes=21)
INDEX (RANGE SCAN) OF DEPT_PK (UNIQUE) (Cost=1 Card=1)
6. 조인과 함께 사용된 서브쿼리의 실행순서
여러 테이블이 조인되는 문장에서 서브쿼리는 가장 나중에 수행되어 조건체크가 이루어지게 되는데, 이 조건체크를 앞당기기 위해서는 PUSH_SUBQ 힌트를 사용한다.