05 Outer 조인

  • NL조이는 그 특성상 Outer 조인할 때 방향이 한쪽으로 고정되며, Outer 기호(+)가 붙지 않은 테이블이 항상 드라이빙 테이블로 선택한다.
  • leading 힌트를 사용해서 순서를 바꿔 보려 해도 소용이 없다.
    {CODE:SQL}

SQL> SELECT /*+ USE_NL( D E ) LEADING( E ) */ *
2 FROM SCOTT.DEPT D
3 , SCOTT.EMP E
4 WHERE E.DEPTNO(+) = D.DEPTNO;

...
15 개의 행이 선택되었습니다.

SQL> @XPLAN






















-

IdOperationNameStartsE-RowsA-RowsA-TimeBuffers0SELECT STATEMENT11500:00:00.0137
1NESTED LOOPS OUTER1141500:00:00.0137
2TABLE ACCESS FULLDEPT14400:00:00.018
  • 3
TABLE ACCESS FULLEMP441400:00:00.0129






















-

Predicate Information (identified by operation id):













---

3 - filter("E"."DEPTNO"="D"."DEPTNO")

{CODE}

ERD 표기를 따르는 SQL 개발의 중요성 ( 이해가 안가신다고 해서 첨부함 ;; )

  • 그림 2-27에서는 사원이 전형 없는 유령 부서가 등록될 수 있다.( null )
    따라서 사원 유무와 상관업이 모든 부서가 출력되도록 하려면 사원 쪽 모든 조건절에 Outer 기호(+)를 반드시 붙여 줘야 한다.

SQL> DESC SCOTT.EMP;
 이름                                                                                                      널?      유형
 ----------------------------------------------------------------------------------------------------------------- -------- -----

 EMPNO                                                                                                     NOT NULL NUMBER(4)
 ENAME                                                                                                              VARCHAR2(10)
 JOB                                                                                                                VARCHAR2(9)
 MGR                                                                                                                NUMBER(4)
 HIREDATE                                                                                                           DATE
 SAL                                                                                                                NUMBER(7,2)
 COMM                                                                                                               NUMBER(7,2)
 DEPTNO                                                                                                             NUMBER(2)

SQL>  SELECT * FROM SCOTT.EMP ORDER BY EMPNO ASC;

     EMPNO ENAME      JOB              MGR HIREDATE        SAL       COMM     DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
      7369 SMITH      CLERK           7902 80/12/17        800                    20
      7499 ALLEN      SALESMAN        7698 81/02/20       1600        300         30
      7521 WARD       SALESMAN        7698 81/02/22       1250        500         30
      7566 JONES      MANAGER         7839 81/04/02       2975                    20
      7654 MARTIN     SALESMAN        7698 81/09/28       1250       1400         30
      7698 BLAKE      MANAGER         7839 81/05/01       2850                    30
      7782 CLARK      MANAGER         7839 81/06/09       2450                    10
      7788 SCOTT      ANALYST         7566 87/04/19       3000                    20
      7839 KING       PRESIDENT            81/11/17       5000                    10
      7844 TURNER     SALESMAN        7698 81/09/08       1500          0         30
      7876 ADAMS      CLERK           7788 87/05/23       1100                    20

     EMPNO ENAME      JOB              MGR HIREDATE        SAL       COMM     DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
      7900 JAMES      CLERK           7698 81/12/03        950                    30
      7902 FORD       ANALYST         7566 81/12/03       3000                    20
      7934 MILLER     CLERK           7782 82/01/23       1300                    10

 SQL>     INSERT INTO SCOTT.EMP VALUES( '7935', 'MILLER', 'CLERK', '7782', '82/01/23', '1300', NULL, NULL );

1 개의 행이 만들어졌습니다.

SQL> COMMIT;

커밋이 완료되었습니다.

SQL> SELECT * FROM SCOTT.EMP ORDER BY EMPNO ASC;

     EMPNO ENAME      JOB              MGR HIREDATE        SAL       COMM     DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
      7369 SMITH      CLERK           7902 80/12/17        800                    20
      7499 ALLEN      SALESMAN        7698 81/02/20       1600        300         30
      7521 WARD       SALESMAN        7698 81/02/22       1250        500         30
      7566 JONES      MANAGER         7839 81/04/02       2975                    20
      7654 MARTIN     SALESMAN        7698 81/09/28       1250       1400         30
      7698 BLAKE      MANAGER         7839 81/05/01       2850                    30
      7782 CLARK      MANAGER         7839 81/06/09       2450                    10
      7788 SCOTT      ANALYST         7566 87/04/19       3000                    20
      7839 KING       PRESIDENT            81/11/17       5000                    10
      7844 TURNER     SALESMAN        7698 81/09/08       1500          0         30
      7876 ADAMS      CLERK           7788 87/05/23       1100                    20

     EMPNO ENAME      JOB              MGR HIREDATE        SAL       COMM     DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
      7900 JAMES      CLERK           7698 81/12/03        950                    30
      7902 FORD       ANALYST         7566 81/12/03       3000                    20
      7934 MILLER     CLERK           7782 82/01/23       1300                    10
      7935 MILLER     CLERK           7782 82/01/23       1300                         <== 이게 바로 유령부서임 ;;

15 개의 행이 선택되었습니다.

SQL>     SELECT TABLE_NAME, CONSTRAINT_NAME, CONSTRAINT_TYPE  FROM DBA_CONSTRAINTS
  2      WHERE OWNER = 'SCOTT';

TABLE_NAME                     CONSTRAINT_NAME                C
------------------------------ ------------------------------ -
EMP                            FK_DEPTNO                      R
DEPT                           PK_DEPT                        P
EMP                            PK_EMP                         P



  • 그림 2-28( page. 272 )에서는 사원이 없는 부서는 등록 될수 없다. ( 식별자 )
    따라서 모든 부서가 출력되도록 하려겨고 굳이 Outer 조인할 필요가 없음에도 Outer 기호(+)를 붙인다면 성능이 나빠질 수 있다.

ERD 표기를 따르는 SQL 개발의 중요성 결론 : 그림 2-17과 2-18 모두 사원 쪽 부서번호가 필수컬럼이다.

소속 부서없이는 사원이 존재할 수 없다는 뜻이므로 테이블을 생성할 때 Not Null 제약을 두어야 한다.

(2) Outer 소트 머지 조인

  • 소트된 중갑 집합을 이용한다는 점만 다를 뿐 처리루틴이 NL조인과 다르지않다고 했다.
  • 따라서 Outer 소트 머지 조인도 처리 방향이 한쪽으로 고정되며, Outer 기호(+)가 붙지 않은 테이블이 항상 First 테이블로 선택된다.
    {CODE:SQL}

SQL> SELECT /*+ USE_MERGE( D E ) LEADING( E ) */ *
2 FROM SCOTT.DEPT D
3 , SCOTT.EMP E
4 WHERE E.DEPTNO(+) = D.DEPTNO;

...
15 개의 행이 선택되었습니다.

SQL> @XPLAN
































-

IdOperationNameStartsE-RowsA-RowsA-TimeBuffersOMem1MemUsed-Mem
































-

0SELECT STATEMENT11500:00:00.0111
1MERGE JOIN OUTER1141500:00:00.0111
2TABLE ACCESS BY INDEX ROWIDDEPT14400:00:00.014
3INDEX FULL SCANPK_DEPT14400:00:00.012
  • 4
SORT JOIN4141400:00:00.017204820482048 (0)
5TABLE ACCESS FULLEMP1141400:00:00.017
































-

Predicate Information (identified by operation id):













---

4 - access("E"."DEPTNO"="D"."DEPTNO")

{CODE}

(3) Outer 해시 조인

  • 소트된 중갑 집합을 이용한다는 점만 다를 뿐 처리루틴이 NL조인과 다르지않다고 했다.
  • 따라서 Outer 소트 머지 조인도 처리 방향이 한쪽으로 고정되며, Outer 기호(+)가 붙지 않은 테이블이 항상 First 테이블로 선택된다.

(3) Outer 해시 조인의 알고리즘 ( page. 275 그림 참조( 일반 Ourter 조인 ) )

  • 1. Outer 집합인 dept 테이블을 해시 테이블로 빌드한다.
  • 2. Inner 집합인 emp 테이블을 읽으면서 해시 테이블을 탐색한다
  • 3. 조인에 성공한 레코드는 곧바로 결과집합에 삽입하고, 조인에 성공했음을 해시 엔트리에 표시해 둔다.
  • 4. Probe 단계가 끝나면 Inner 조인과 동일한 결과집합이 만들어진 상태이다. 이제 조인에 실패 했던 레코드를 결과집합에 포함시켜야 하므로 해시 테이블을 스캔하면서
    체크가 없는 dept 엔트리를 결과집합에 삽입한다.

{CODE:SQL}

SQL> SELECT /*+ USE_HASH( D E ) LEADING( E ) */ *
2 FROM TEST_DEPT D
3 , TEST_EMP E
4 WHERE E.DEPTNO(+) = D.DEPTNO;

DEPTNO DNAME LOC EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO



--


--


-

--

--

-

--



--

--

--
20 RESEARCH DALLAS 7369 SMITH CLERK 7902 80/12/17 800 20
30 SALES CHICAGO 7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30
30 SALES CHICAGO 7521 WARD SALESMAN 7698 81/02/22 1250 500 30
20 RESEARCH DALLAS 7566 JONES MANAGER 7839 81/04/02 2975 20
30 SALES CHICAGO 7654 MARTIN SALESMAN 7698 81/09/28 1250 1400 30
30 SALES CHICAGO 7698 BLAKE MANAGER 7839 81/05/01 2850 30
10 ACCOUNTING NEW YORK 7782 CLARK MANAGER 7839 81/06/09 2450 10
20 RESEARCH DALLAS 7788 SCOTT ANALYST 7566 87/04/19 3000 20
10 ACCOUNTING NEW YORK 7839 KING PRESIDENT 81/11/17 5000 10
30 SALES CHICAGO 7844 TURNER SALESMAN 7698 81/09/08 1500 0 30
20 RESEARCH DALLAS 7876 ADAMS CLERK 7788 87/05/23 1100 20
30 SALES CHICAGO 7900 JAMES CLERK 7698 81/12/03 950 30
20 RESEARCH DALLAS 7902 FORD ANALYST 7566 81/12/03 3000 20
10 ACCOUNTING NEW YORK 7934 MILLER CLERK 7782 82/01/23 1300 10
50 TEST TEST_LOC
40 OPERATIONS BOSTON

16 개의 행이 선택되었습니다.

SQL> @XPLAN






























-

IdOperationNameStartsE-RowsA-RowsA-TimeBuffersOMem1MemUsed-Mem






























-

0SELECT STATEMENT11600:00:00.0111
  • 1
HASH JOIN OUTER1141600:00:00.01111011K1011K743K (0)
2TABLE ACCESS FULLTEST_DEPT15500:00:00.017
3TABLE ACCESS FULLTEST_EMP1141400:00:00.014






























-

Predicate Information (identified by operation id):













---

1 - access("E"."DEPTNO"="D"."DEPTNO")

-- 역순
SQL> SELECT /*+ USE_HASH( D E ) LEADING( E ) */ *
2 FROM (SELECT IN_E.*, ROWNUM FROM TEST_DEPT IN_E ORDER BY DEPTNO DESC ) D
3 , TEST_EMP E
4 WHERE E.DEPTNO(+) = D.DEPTNO;

DEPTNO DNAME LOC ROWNUM EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO



--


--


-

--

--

--

-

--



--

--

--
20 RESEARCH DALLAS 4 7369 SMITH CLERK 7902 80/12/17 800 20
30 SALES CHICAGO 3 7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30
30 SALES CHICAGO 3 7521 WARD SALESMAN 7698 81/02/22 1250 500 30
20 RESEARCH DALLAS 4 7566 JONES MANAGER 7839 81/04/02 2975 20
30 SALES CHICAGO 3 7654 MARTIN SALESMAN 7698 81/09/28 1250 1400 30
30 SALES CHICAGO 3 7698 BLAKE MANAGER 7839 81/05/01 2850 30
10 ACCOUNTING NEW YORK 5 7782 CLARK MANAGER 7839 81/06/09 2450 10
20 RESEARCH DALLAS 4 7788 SCOTT ANALYST 7566 87/04/19 3000 20
10 ACCOUNTING NEW YORK 5 7839 KING PRESIDENT 81/11/17 5000 10
30 SALES CHICAGO 3 7844 TURNER SALESMAN 7698 81/09/08 1500 0 30
20 RESEARCH DALLAS 4 7876 ADAMS CLERK 7788 87/05/23 1100 20
30 SALES CHICAGO 3 7900 JAMES CLERK 7698 81/12/03 950 30
20 RESEARCH DALLAS 4 7902 FORD ANALYST 7566 81/12/03 3000 20
10 ACCOUNTING NEW YORK 5 7934 MILLER CLERK 7782 82/01/23 1300 10
50 TEST TEST_LOC 1
40 OPERATIONS BOSTON 2

16 개의 행이 선택되었습니다.

SQL> @xplan


































IdOperationNameStartsE-RowsA-RowsA-TimeBuffersOMem1MemUsed-Mem


































0SELECT STATEMENT11600:00:00.017
  • 1
HASH JOIN OUTER1141600:00:00.017972K972K751K (0)
2VIEW15500:00:00.013
3COUNT1500:00:00.013
4TABLE ACCESS BY INDEX ROWIDTEST_DEPT15500:00:00.013
5INDEX FULL SCAN DESCENDINGTEST_DEPT_PK15500:00:00.011
6TABLE ACCESS FULLTEST_EMP1141400:00:00.014


































Predicate Information (identified by operation id):













---

1 - access("E"."DEPTNO"="D"."DEPTNO")

{CODE}

Right Outer 해시 조인

  • Outer 조인할때 조인 순서가 고정되므로 자주 성능문제를 일으키곤 했다.
  • ex : 주문테이블을 기준으로 고객테이블과 Outer 조인하는 경우에 대용량 주문 테이블을 해시 테이블로 빌드해야 하는 문제가 발생
  • Hash Area가 부족해 디스크 쓰기와 읽기가 밸생할 뿐만 아니라 주문 건수가 많은 고객일수록 해시 버킷 당 엔트리 개수가 많아져 해시 테이블을 탐색하는 효율이 크게 저하된다.
  • Right Outer 해시 조인은 결구 Outer NL 조인과 같은 알고리즘을 사용한다.

{CODE:SQL}

Right Outer 해시 조인 알고리즘 ( page. 275 그림 참조( 일반 Ourter 조인 ) )

  • 1. Inner 집합인 dept 테이블을 해시 테이블로 빌드한다
  • 2. Outer 집합인 emp 테이블을 읽으면서 해시 테이블을 탐색한다.
  • 3. Outer 조인이므로 조인 성공 여부에 상관없이 결과집합에 삽입한다.

SQL> SELECT /*+ USE_HASH( D E ) SWAP_JOIN_INPUTS( E ) */ *
2 FROM TEST_DEPT D
3 , TEST_EMP E
4 WHERE E.DEPTNO(+) = D.DEPTNO;

DEPTNO DNAME LOC EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO



--


--


-

--

--

-

--



--

--

--
10 ACCOUNTING NEW YORK 7934 MILLER CLERK 7782 82/01/23 1300 10
10 ACCOUNTING NEW YORK 7839 KING PRESIDENT 81/11/17 5000 10
10 ACCOUNTING NEW YORK 7782 CLARK MANAGER 7839 81/06/09 2450 10
20 RESEARCH DALLAS 7902 FORD ANALYST 7566 81/12/03 3000 20
20 RESEARCH DALLAS 7876 ADAMS CLERK 7788 87/05/23 1100 20
20 RESEARCH DALLAS 7788 SCOTT ANALYST 7566 87/04/19 3000 20
20 RESEARCH DALLAS 7566 JONES MANAGER 7839 81/04/02 2975 20
20 RESEARCH DALLAS 7369 SMITH CLERK 7902 80/12/17 800 20
30 SALES CHICAGO 7900 JAMES CLERK 7698 81/12/03 950 30
30 SALES CHICAGO 7844 TURNER SALESMAN 7698 81/09/08 1500 0 30
30 SALES CHICAGO 7698 BLAKE MANAGER 7839 81/05/01 2850 30

DEPTNO DNAME LOC EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO



--


--


-

--

--

-

--



--

--

--
30 SALES CHICAGO 7654 MARTIN SALESMAN 7698 81/09/28 1250 1400 30
30 SALES CHICAGO 7521 WARD SALESMAN 7698 81/02/22 1250 500 30
30 SALES CHICAGO 7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30
40 OPERATIONS BOSTON
50 TEST TEST_LOC

16 개의 행이 선택되었습니다.

SQL> @XPLAN































IdOperationNameStartsE-RowsA-RowsA-TimeBuffersOMem1MemUsed-Mem































0SELECT STATEMENT11600:00:00.0111
  • 1
HASH JOIN RIGHT OUTER1141600:00:00.0111865K865K646K (0)
2TABLE ACCESS FULLTEST_EMP1141400:00:00.013
3TABLE ACCESS FULLTEST_DEPT15500:00:00.018































Predicate Information (identified by operation id):













---

1 - access("E"."DEPTNO"="D"."DEPTNO")

-- 역순
SQL> SELECT /*+ USE_HASH( D E ) SWAP_JOIN_INPUTS( E ) */ *
2 FROM (SELECT IN_E.*, ROWNUM FROM TEST_DEPT IN_E ORDER BY DEPTNO DESC ) D
3 ,TEST_EMP E
4 WHERE E.DEPTNO(+) = D.DEPTNO;

DEPTNO DNAME LOC ROWNUM EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO



--


--


-

--

--

--

-

--



--

--

--
50 TEST TEST_LOC 1
40 OPERATIONS BOSTON 2
30 SALES CHICAGO 3 7900 JAMES CLERK 7698 81/12/03 950 30
30 SALES CHICAGO 3 7844 TURNER SALESMAN 7698 81/09/08 1500 0 30
30 SALES CHICAGO 3 7698 BLAKE MANAGER 7839 81/05/01 2850 30
30 SALES CHICAGO 3 7654 MARTIN SALESMAN 7698 81/09/28 1250 1400 30
30 SALES CHICAGO 3 7521 WARD SALESMAN 7698 81/02/22 1250 500 30
30 SALES CHICAGO 3 7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30
20 RESEARCH DALLAS 4 7902 FORD ANALYST 7566 81/12/03 3000 20
20 RESEARCH DALLAS 4 7876 ADAMS CLERK 7788 87/05/23 1100 20
20 RESEARCH DALLAS 4 7788 SCOTT ANALYST 7566 87/04/19 3000 20

DEPTNO DNAME LOC ROWNUM EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO



--


--


-

--

--

--

-

--



--

--

--
20 RESEARCH DALLAS 4 7566 JONES MANAGER 7839 81/04/02 2975 20
20 RESEARCH DALLAS 4 7369 SMITH CLERK 7902 80/12/17 800 20
10 ACCOUNTING NEW YORK 5 7934 MILLER CLERK 7782 82/01/23 1300 10
10 ACCOUNTING NEW YORK 5 7839 KING PRESIDENT 81/11/17 5000 10
10 ACCOUNTING NEW YORK 5 7782 CLARK MANAGER 7839 81/06/09 2450 10

16 개의 행이 선택되었습니다.

SQL> @XPLAN

Plan hash value: 663895112




































-

IdOperationNameStartsE-RowsA-RowsA-TimeBuffersReadsOMem1MemUsed-Mem




































-

0SELECT STATEMENT11600:00:00.0171
  • 1
HASH JOIN RIGHT OUTER1141600:00:00.0171865K865K700K (0)
2TABLE ACCESS FULLTEST_EMP1141400:00:00.0130
3VIEW15500:00:00.0141
4COUNT1500:00:00.0141
5TABLE ACCESS BY INDEX ROWIDTEST_DEPT15500:00:00.0141
6INDEX FULL SCAN DESCENDINGTEST_DEPT_PK15500:00:00.0121




































-

Predicate Information (identified by operation id):













---

1 - access("E"."DEPTNO"="D"."DEPTNO")

{CODE}

Right Outer 해시 조인 탄생배경

  • 고객 없는 주문 또는 상품 없는 주문은 존재 하지 않도록 설계되었다.
  • 오라클은 이런 사실을 감안해 Outer 테이블을 해시 테이블로 빌드하는 알고리즘을 애초에 선택하였다.
  • Inner 조인하고 나서 해시 테이블을 전체적으로 한 번 더 스캔하는 비효율을 감수하면서 까지..( 작은 쪽 집합을 해시 테이블로 빌드하는 게 유리하므로 )
  • 고객 없는 주문 또는 상품 없는 주문은 존재 하지 않도록 설계되었지만 FK를 설정하지 않은 채 운영하다보니 잘못된 레코드가 생성됨.
  • 어쩔수 없이 대용량 테이블인 주문 테이블을 빌드해야함.
  • 오라클은 이런 성능 이슈를 해결하기 위하여 10부터 Inner쪽 집합을 해시 테이블로 빌드할 수 있는 알고리즘 추가함.

9i 이전 버전에서 Outer 해시 조인 튜닝

{CODE:SQL}
SQL> SELECT /*+ ORDERED INDEX_FFS( E ) FULL( D ) FULL( E2 ) USE_HASH( E D E2) PARALLEL_INDEX( E ) PARALLEL( D ) PARALLEL( E2 ) / D., E2.*
2 FROM TEST_EMP E, TEST_DEPT D, TEST_EMP E2
3 WHERE E.DEPTNO = D.DEPTNO(+)
4 AND E.EMPNO = E2.EMPNO ;

DEPTNO DNAME LOC EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO



--


--


-

--

--

-

--



--

--

--
20 RESEARCH DALLAS 7876 ADAMS CLERK 7788 87/05/23 1100 20
20 RESEARCH DALLAS 7788 SCOTT ANALYST 7566 87/04/19 3000 20
20 RESEARCH DALLAS 7902 FORD ANALYST 7566 81/12/03 3000 20
10 ACCOUNTING NEW YORK 7839 KING PRESIDENT 81/11/17 5000 10
10 ACCOUNTING NEW YORK 7934 MILLER CLERK 7782 82/01/23 1300 10
30 SALES CHICAGO 7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30
30 SALES CHICAGO 7844 TURNER SALESMAN 7698 81/09/08 1500 0 30
20 RESEARCH DALLAS 7369 SMITH CLERK 7902 80/12/17 800 20
10 ACCOUNTING NEW YORK 7782 CLARK MANAGER 7839 81/06/09 2450 10
30 SALES CHICAGO 7900 JAMES CLERK 7698 81/12/03 950 30
30 SALES CHICAGO 7521 WARD SALESMAN 7698 81/02/22 1250 500 30

DEPTNO DNAME LOC EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO



--


--


-

--

--

-

--



--

--

--
30 SALES CHICAGO 7698 BLAKE MANAGER 7839 81/05/01 2850 30
20 RESEARCH DALLAS 7566 JONES MANAGER 7839 81/04/02 2975 20
30 SALES CHICAGO 7654 MARTIN SALESMAN 7698 81/09/28 1250 1400 30

14 개의 행이 선택되었습니다.

SQL> @XPLAN

































--

IdOperationNameStartsE-RowsA-RowsA-TimeBuffersOMem1MemUsed-Mem

































--

0SELECT STATEMENT11400:00:00.039
1PX COORDINATOR11400:00:00.039
2PX SEND QC (RANDOM):TQ10004014000:00:00.010
  • 3
HASH JOIN BUFFERED014000:00:00.0101155K1155K813K (0)
4PX RECEIVE014000:00:00.010
5PX SEND HASH:TQ10002014000:00:00.010
  • 6
HASH JOIN OUTER BUFFERED014000:00:00.0101524K1524K566K (0)
7PX RECEIVE014000:00:00.010
8PX SEND HASH:TQ10000014000:00:00.010
9PX BLOCK ITERATOR014000:00:00.010
  • 10
INDEX FAST FULL SCANTEST_EMP_PK014000:00:00.010
11PX RECEIVE05000:00:00.010
12PX SEND HASH:TQ1000105000:00:00.010
13PX BLOCK ITERATOR05000:00:00.010
  • 14
TABLE ACCESS FULLTEST_DEPT05000:00:00.010
15PX RECEIVE014000:00:00.010
16PX SEND HASH:TQ10003014000:00:00.010
17PX BLOCK ITERATOR014000:00:00.010
  • 18
TABLE ACCESS FULLTEST_EMP014000:00:00.010

































--

Predicate Information (identified by operation id):













---

3 - access("E"."EMPNO"="E2"."EMPNO")
6 - access("E"."DEPTNO"="D"."DEPTNO")
10 - access(:Z>=:Z AND :Z<=:Z)
14 - access(:Z>=:Z AND :Z<=:Z)
18 - access(:Z>=:Z AND :Z<=:Z)

{CODE}

  • Outer 집합인 E 테이블의 PK만 빠르게 읽어 인덱스 블록만 읽더라도 In-Memory 해시 조인은 불가능하겠지만 Build Input 크기를 줄임으로써 디스크 쓰기 및 읽기 작업을 최소화하려는 아이디어
  • 디스크 쓰기와 읽기 작업을 줄여주는 효과는 있지만 해시 버킷 당 엔트리 개수가 많아서 생기는 문제는 필할수없다.
  • 만약 주문 테이블이 주문일시 Range 파티션 테이블이라면, 일정한 주문일시 구간 내에서의 고객별 주문 건수는 많지 않을 것이기 때문에해시 버킷 당 엔트리 개수를 최소화 할 수 있다
  • 그러면 고객 테이블을 반복적으로 읽는 비효율에도 불구하고 더 빠르게 수행될 것이다.
    h3 Full Outer 조인
    {CODE:SQL}

SQL> EXEC DBMS_RANDOM.SEED( 150 ) ;

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> CREATE TABLE 입금 AS
2 SELECT ROWNUM 일련번호
3 , ROUND(DBMS_RANDOM.VALUE(1, 20)) 고객ID
4 , ROUND(DBMS_RANDOM.VALUE(1000, 1000000), -2) 입금액
5 FROM dual CONNECT BY LEVEL <= 10
6 ;

테이블이 생성되었습니다.

SQL> CREATE TABLE 출금 AS
2 SELECT ROWNUM 일련번호
3 , ROUND(DBMS_RANDOM.VALUE(1, 20)) 고객ID
4 , ROUND(DBMS_RANDOM.VALUE(1000, 1000000), -2) 출금액
5 FROM dual CONNECT BY LEVEL <= 10;

테이블이 생성되었습니다.

SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, '입금');

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, '출금');

PL/SQL 처리가 정상적으로 완료되었습니다.

{CODE}

Left Outer 조인 + Union all + Anti 조인( Not Exists 필터 ) 이용

{CODE:SQL}

SQL> SELECT A.고객ID, A.입금액, B.출금액
2 FROM ( SELECT 고객ID, SUM( 입금액 ) 입금액 FROM 입금 GROUP BY 고객ID ) A
3 , ( SELECT 고객ID, SUM( 출금액 ) 출금액 FROM 출금 GROUP BY 고객ID ) B
4 WHERE B.고객ID(+) = A.고객ID
5 UNION ALL
6 SELECT 고객ID, NULL, 출금액
7 FROM (SELECT 고객ID, SUM( 출금액 ) 출금액 FROM 출금 GROUP BY 고객ID) A
8 WHERE NOT EXISTS (SELECT 'X' FROM 입금 WHERE 고객ID = A.고객ID);

고객ID 입금액 출금액



--

--

--
13 59400 83100
4 1211700 76200
3 259400 701000
18 336900 1445600
19 398300 781200
8 957000
1 224100
2 231900
6 707000
11 70300
17 296700

고객ID 입금액 출금액



--

--

--
9 382600

12 개의 행이 선택되었습니다.

SQL> @XPLAN





























---

IdOperationNameStartsE-RowsA-RowsA-TimeBuffersOMem1MemUsed-Mem





























---

0SELECT STATEMENT11200:00:00.0112
1UNION-ALL11200:00:00.0112
  • 2
HASH JOIN OUTER19900:00:00.0161269K1269K1144K (0)
3VIEW19900:00:00.013
4HASH GROUP BY19900:00:00.013948K948K2206K (0)
5TABLE ACCESS FULL입금1101000:00:00.013
6VIEW18800:00:00.013
7HASH GROUP BY18800:00:00.013948K948K1705K (0)
8TABLE ACCESS FULL출금1101000:00:00.013
9HASH GROUP BY18300:00:00.016948K948K709K (0)
  • 10
HASH JOIN ANTI110300:00:00.0161269K1269K912K (0)
11TABLE ACCESS FULL출금1101000:00:00.013
12TABLE ACCESS FULL입금1101000:00:00.013





























---

Predicate Information (identified by operation id):













---

2 - access("B"."고객ID"="A"."고객ID")
10 - access("고객ID"="고객ID")

{CODE}

ANSI FULL Outer 조인( Native Hash Full Outer 조인 )

  • 9i
  • Left Outer 조인 + Union all + Anti 조인( Not Exists 필터 ) 이용 같은 실행계획 수립
  • 10.2.0.4 : opt_param('_optimizer_native_full_outer_join', 'force')
    {CODE:SQL}
    SQL> SELECT nvl(A.고객ID, B.고객ID), A.입금액, B.출금액
    2 FROM ( SELECT 고객ID, SUM( 입금액 ) 입금액 FROM 입금 GROUP BY 고객ID ) A
    3 FULL OUTER JOIN
    4 ( SELECT 고객ID, SUM( 출금액 ) 출금액 FROM 출금 GROUP BY 고객ID ) B
    5 ON A.고객ID = B.고객ID;

NVL(A.고객ID,B.고객ID) 입금액 출금액






--

--

--
1 224100
6 707000
13 59400 83100
2 231900
4 1211700 76200
8 957000
3 259400 701000
18 336900 1445600
19 398300 781200
11 70300
17 296700
9 382600

12 개의 행이 선택되었습니다.

SQL> @XPLAN






























---

IdOperationNameStartsE-RowsA-RowsA-TimeBuffersOMem1MemUsed-Mem






























---

0SELECT STATEMENT11200:00:00.016
1VIEWVW_FOJ_0191200:00:00.016
  • 2
HASH JOIN FULL OUTER191200:00:00.0161269K1269K885K (0)
3VIEW18800:00:00.013
4HASH GROUP BY18800:00:00.013948K948K1706K (0)
5TABLE ACCESS FULL출금1101000:00:00.013
6VIEW19900:00:00.013
7HASH GROUP BY19900:00:00.013948K948K2210K (0)
8TABLE ACCESS FULL입금1101000:00:00.013






























---

Predicate Information (identified by operation id):













---

2 - access("A"."고객ID"="B"."고객ID")
{CODE}

  • 1. 출금테이블을 해시 테이블로 빌드 한다.
  • 2. 입금테이블로 해시 테이블을 탐색하면서 조인한다.
  • 3. 조인 성공여부에 상관없이 결과 집합에 삽입하고, 조인에 성공한 출금 래코드에 체크 표시를 해 둔다.
  • 4. Probe 단계가 끝나면 Right Outer 해시 조인한 것과 동일한 결과집합이 만들어진다. 이제 해시 테이블을 스캔하면서
    체크 표시가 없는 출금레코드를 결과집합에 삽입함으로써 Full Outer 조인을 완성한다. 입금액이 마지막에 출력된 이유가 바로 이것이다.

Union All을 이용한 Full Outer 조인

{CODE:SQL}

SQL> SELECT 고객ID, SUM( 입금액 ) 입금액, SUM( 출금액 ) 출금액
2 FROM (
3 SELECT 고객ID, 입금액, TO_NUMBER(NULL) 출금액 FROM 입금
4 UNION ALL
5 SELECT 고객ID, TO_NUMBER(NULL) , 출금액 FROM 출금
6 )
7 GROUP BY 고객ID;

고객ID 입금액 출금액



--

--

--
1 224100
6 707000
13 59400 83100
11 70300
2 231900
4 1211700 76200
8 957000
17 296700
3 259400 701000
18 336900 1445600
19 398300 781200
9 382600

12 개의 행이 선택되었습니다.

SQL> @XPLAN





























--

IdOperationNameStartsE-RowsA-RowsA-TimeBuffersOMem1MemUsed-Mem





























--

0SELECT STATEMENT11200:00:00.016
1HASH GROUP BY191200:00:00.016843K843K1213K (0)
2VIEW1202000:00:00.016
3UNION-ALL12000:00:00.016
4TABLE ACCESS FULL입금1101000:00:00.013
5TABLE ACCESS FULL출금1101000:00:00.013





























--

19 개의 행이 선택되었습니다.

{CODE}