오타와 오류를 발견하신 독자는 댓글을 이용해 주세요.


Page

부분

수정 전

수정 후

53

밑에서 세 번째 줄

~DBA들의 취약점 중 많은 부분이 장의 내용을~

~DBA들의 취약점 중 많은 부분이 장의 내용을~

77

outline data

ELIMINATE_JOIN(@"SEL$1" "P"@"SEL$1")

ELIMINATE_JOIN(@"SEL$1" "D"@"SEL$1")

80

제목

2.4 OJE Outer-Join Elimination)

2.4 OJE (Outer-Join Elimination)

104

SQL 윗부분

SELECT e.employee_id, e.email, d.department_id

SELECT /*+ no_merge(@sel$1)  */
         e.employee_id, e.email, d.department_id

104

SQL 아랫부분

SQL의 결과는 아래와 같다.

SQL에 힌트를 사용한 이유는 Lateral View를 보존하기 위해서이다. View Merge 된다면 실행계획에서 Lateral View를 볼 수 없다. SQL의 결과는 아래와 같다.

106

직원구분코드 컬럼생성 부분

ALTER TABLE EMPLOYEE ADD EMP_KIND VARCHAR2(1) DEFAULT '1' NOT NULL;

ALTER TABLE EMPLOYEE ADD EMP_KIND VARCHAR2(1) DEFAULT 1 NOT NULL;

108

SQL 윗부분

SELECT /*+ GATHER_PLAN_STATISTICS ORDERED */

SELECT /*+ GATHER_PLAN_STATISTICS ORDERED NO_MERGE(@SEL$1) NO_MERGE(@SEL$3) */

109

위에서 네 번째 줄

ANSI SQL을 이용함으로써 선택적으로 ~

여기서도 View Merging을 발생하지 않게 하기위해 NO_MERGE 힌트를 사용하였다. ANSI SQL을 이용함으로써 선택적으로~

109

위에서 일곱 번째 줄

ANSI SQL을 사용할 수 없는 경우는 아래와 같이 조인절에 DECODE CASE 문을 사용하여도 같은 효과를 누릴 수 있다.

삭제 후 추가될 내용



내용이 많아 첨부파일로 처리함.

109

밑에서 다섯 번째 줄

Lateral View ANSI SQL 뿐만 아니라
일반적인 뷰를 Outer 조인하는 경우, ~

Lateral View 사용하지 않으면서도 선택적으로 조인하고 있다. 이 방법은 ANSI SQL을 사용할 수 없는 환경에서 훌륭한 해결책이 될 것이다.

Lateral View
ANSI SQL 뿐만 아니라 일반적인 뷰를 Outer 조인하는 경우, ~

120

위에서 두 번째 줄

Subsumtion

Subsumption

121

SQL 윗부분

Subsumtion

Subsumption

158

위에서 아홉 번째 줄

따라서 Null인 데이터를 찾자마자 Scan
 
멈출 수 있는 것이다.

따라서 Null인 데이터를 찾자마자 Scan을 멈출 수 있는 것이다. 추가될 내용의 위치




내용이 많아 첨부파일로 처리함.
관련내용: http://scidb.tistory.com/120

158, 159

158 페이지 위에서 열 번째 줄부터 ~ 159 페이지 세 번째 줄 까지

하지만 모든 Not In 서브쿼리에 Is Null 조건을 추가하면 결과가 틀려지지 않을까?  부터

이처럼 서브쿼리의 조건절이 추가된다면 그에 따라 적응적 탐색(Adaptive Null Aware Scan)을 하므로 걱정할 것이 없다.
까지 SQL과 실행계획 포함하여 모두 삭제

삭제 후 추가될 내용



내용이 많아 첨부파일로 처리함.
관련내용: http://scidb.tistory.com/121

162

10053 Trace 위의 제약사항 부분

두 번째로는 Where 절의 Is Null 조건에는 Outer 쪽 테이블의 PK 컬럼만 올 수 있다. 물론 PK 컬럼으로 조인이 되어야 함은 당연한 것이다.

두 번째로는 Outer Join에 사용된 컬럼과 Is Null 조건에 사용된 컬럼이 동일해야 한다. d.department_idOuter Join하고 d.department_name으로 Is Null 조건을 주면 OJTAJ가 발생되지 않는다.

105

1)번 부분

employee department간의 관계가 N:1 이면서 department 쪽이 Outer Join을 사용하였기 때문이다.

employee department간의 관계가 N:1 이면서 department 쪽이 Outer Join을 사용하였기 때문이다. 반대로 메인쿼리와 Lateral View 1:N의 관계라면 Lateral View는 결과건수에 영향을 미친다.

105

3)번 부분

이러한 모든 상황에서 Later View 내부의 테이블에 ~

이러한 모든 상황에서 Lateral View 내부의 테이블에 ~

197

Column Projection Information 윗부분

10053 Trace에는 파라미터 Pivot2를 적용할 경우 SQL 정보가 없다. 부터 197페이지 마지막 까지 모두 삭제

삭제 후 추가될 내용

 

내용이 많아 첨부파일로 처리함.




빨강색은 삭제이고 파랑색은 추가 입니다.


'The Logical Optimizer' 카테고리의 다른 글

The Logical Optimizer Part 1 - PPT  (17) 2010.07.26
The Logical Optimizer-서점  (0) 2010.04.27
The Logical Optimizer-Script Download  (37) 2010.04.20
The Logical Optimizer-오타와 오류등록  (26) 2010.04.20
저자와의 대화  (36) 2010.04.20
The Logical Optimizer  (61) 2010.04.05
Posted by extremedb

댓글을 달아 주세요

  1. 2010.04.21 22:54  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  2. 2010.04.28 22:13  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  3. Favicon of http://www.interpark.com BlogIcon 피아체 2010.04.30 14:51  댓글주소  수정/삭제  댓글쓰기

    테스트 버전은 오라클 11g R2 입니다.
    108page lateral view 관련 내용입니다.

    덕분에 아주 좋은 내용을 접한것 같습니다.
    ansi sql은 잘몰랐는데..많은 도움이 되네요.
    두번째 쿼리에서 buffers 가 0이 되어야 하는데 1로 첫번째 쿼리랑 같은데 확인 부탁 드립니다.

    EMPLOYEE.EMP_KIND 가 VARCHAR2(1) 인데 DEFAULT 값이 1로 되어 있어서 '1' 수정했습니다.
    물론 그전에는 1 로 테스트 했었구요.

    ALTER TABLE EMPLOYEE ADD EMP_KIND VARCHAR2(1) DEFAULT '1' NOT NULL;

    UPDATE EMPLOYEE SET EMP_KIND = CASE WHEN MOD(EMPLOYEE_ID, 2) = 1 THEN '1' ELSE '2' END;
    COMMIT;

    CREATE TABLE EMPLOYEE_KIND1 AS
    SELECT EMPLOYEE_ID, PHONE_NUMBER||TO_CHAR(ROWNUM) AS HOME_PHONE_NUMBER
    FROM EMPLOYEE
    WHERE EMP_KIND = '1';

    CREATE TABLE EMPLOYEE_KIND2 AS
    SELECT EMPLOYEE_ID, PHONE_NUMBER||TO_CHAR(ROWNUM) AS OFFICE_PHONE_NUMBER
    FROM EMPLOYEE
    WHERE EMP_KIND = '2';

    ALTER TABLE EMPLOYEE_KIND1 ADD CONSTRAINT PK_EMPLOYEE_KIND1 PRIMARY KEY (EMPLOYEE_ID) USING INDEX;
    ALTER TABLE EMPLOYEE_KIND2 ADD CONSTRAINT PK_EMPLOYEE_KIND2 PRIMARY KEY (EMPLOYEE_ID) USING INDEX;






    1번쿼리
    SELECT /*+ GATHER_PLAN_STATISTICS ORDERED */
    E.EMPLOYEE_ID, E.FIRST_NAME, E.LAST_NAME, E.EMP_KIND
    , K1.HOME_PHONE_NUMBER
    , K2.OFFICE_PHONE_NUMBER
    FROM EMPLOYEE E, EMPLOYEE_KIND1 K1, EMPLOYEE_KIND2 K2
    WHERE E.EMPLOYEE_ID = K1.EMPLOYEE_ID(+)
    AND E.EMPLOYEE_ID = K2.EMPLOYEE_ID(+)
    AND E.EMPLOYEE_ID = 133;

    SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST -ROWS +PREDICATE'));

    ----------------------------------------------------------------------------------------------------
    | Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
    ----------------------------------------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | 1 | 1 |00:00:00.01 | 5 |
    | 1 | NESTED LOOPS OUTER | | 1 | 1 |00:00:00.01 | 5 |
    | 2 | NESTED LOOPS OUTER | | 1 | 1 |00:00:00.01 | 4 |
    | 3 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE | 1 | 1 |00:00:00.01 | 2 |
    |* 4 | INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | 1 |00:00:00.01 | 1 |
    | 5 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND1 | 1 | 1 |00:00:00.01 | 2 |
    |* 6 | INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND1 | 1 | 1 |00:00:00.01 | 1 |
    | 7 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND2 | 1 | 0 |00:00:00.01 | 1 |
    |* 8 | INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND2 | 1 | 0 |00:00:00.01 | 1 | ->비효율이 발생한다고 하셨는데.
    ----------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ---------------------------------------------------

    4 - access("E"."EMPLOYEE_ID"=133)
    6 - access("K1"."EMPLOYEE_ID"=133)
    8 - access("K2"."EMPLOYEE_ID"=133)


    2번쿼리
    SELECT /*+ GATHER_PLAN_STATISTICS ORDERED */
    E.EMPLOYEE_ID, E.FIRST_NAME, E.LAST_NAME, E.EMP_KIND
    , K1.HOME_PHONE_NUMBER
    , K2.OFFICE_PHONE_NUMBER
    FROM EMPLOYEE E
    LEFT OUTER JOIN
    EMPLOYEE_KIND1 K1 ON (
    E.EMPLOYEE_ID = K1.EMPLOYEE_ID AND E.EMP_KIND = '1'
    )
    LEFT OUTER JOIN
    EMPLOYEE_KIND2 K2 ON (
    E.EMPLOYEE_ID = K2.EMPLOYEE_ID AND E.EMP_KIND = '2'
    )
    WHERE E.EMPLOYEE_ID = 133;

    SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST -ROWS +PREDICATE'));
    ----------------------------------------------------------------------------------------------------
    | Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
    ----------------------------------------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | 1 | 1 |00:00:00.01 | 5 |
    | 1 | NESTED LOOPS OUTER | | 1 | 1 |00:00:00.01 | 5 |
    | 2 | NESTED LOOPS OUTER | | 1 | 1 |00:00:00.01 | 4 |
    | 3 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE | 1 | 1 |00:00:00.01 | 2 |
    |* 4 | INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | 1 |00:00:00.01 | 1 |
    | 5 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND1 | 1 | 1 |00:00:00.01 | 2 |
    |* 6 | INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND1 | 1 | 1 |00:00:00.01 | 1 |
    | 7 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND2 | 1 | 0 |00:00:00.01 | 1 |
    |* 8 | INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND2 | 1 | 0 |00:00:00.01 | 1 | -- 여기에서는 1로 잡히네요
    ----------------------------------------------------------------------------------------------------


    Predicate Information (identified by operation id):
    ---------------------------------------------------

    4 - access("E"."EMPLOYEE_ID"=133)
    6 - access("K1"."EMPLOYEE_ID"=133)
    filter(NVL("E"."EMP_KIND",'1')=CASE WHEN ("K1"."EMPLOYEE_ID" IS NOT NULL) THEN '1' ELSE '1' END )
    8 - access("K2"."EMPLOYEE_ID"=133)
    filter(NVL("E"."EMP_KIND",'1')=CASE WHEN ("K2"."EMPLOYEE_ID" IS NOT NULL) THEN '2' ELSE '2' END )
    3번쿼리
    SELECT /*+ GATHER_PLAN_STATISTICS ORDERED */
    E.EMPLOYEE_ID, E.FIRST_NAME, E.LAST_NAME, E.EMP_KIND
    , K1.HOME_PHONE_NUMBER
    , K2.OFFICE_PHONE_NUMBER
    FROM EMPLOYEE E, EMPLOYEE_KIND1 K1, EMPLOYEE_KIND2 K2
    WHERE DECODE(E.EMP_KIND, '1', E.EMPLOYEE_ID) = K1.EMPLOYEE_ID(+)
    AND DECODE(E.EMP_KIND, '2', E.EMPLOYEE_ID) = K2.EMPLOYEE_ID(+)
    AND E.EMPLOYEE_ID = 133;

    SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST -ROWS +PREDICATE'));

    ----------------------------------------------------------------------------------------------------
    | Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
    ----------------------------------------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | 1 | 1 |00:00:00.01 | 4 |
    | 1 | NESTED LOOPS OUTER | | 1 | 1 |00:00:00.01 | 4 |
    | 2 | NESTED LOOPS OUTER | | 1 | 1 |00:00:00.01 | 4 |
    | 3 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE | 1 | 1 |00:00:00.01 | 2 |
    |* 4 | INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | 1 |00:00:00.01 | 1 |
    | 5 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND1 | 1 | 1 |00:00:00.01 | 2 |
    |* 6 | INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND1 | 1 | 1 |00:00:00.01 | 1 |
    | 7 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND2 | 1 | 0 |00:00:00.01 | 0 |
    |* 8 | INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND2 | 1 | 0 |00:00:00.01 | 0 | -- 0으로 나타납니다.
    ----------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ---------------------------------------------------

    4 - access("E"."EMPLOYEE_ID"=133)
    6 - access("K1"."EMPLOYEE_ID"=DECODE(NVL("E"."EMP_KIND",'1'),'1',"E"."EMPLOYEE_ID";))
    8 - access("K2"."EMPLOYEE_ID"=DECODE(NVL("E"."EMP_KIND",'1'),'2',"E"."EMPLOYEE_ID";))

    • Favicon of https://scidb.tistory.com BlogIcon extremedb 2010.04.30 15:15 신고  댓글주소  수정/삭제

      2번째 쿼리에서 비효율이 발생한 이유는 옵티마이져의 잘못입니다. view merging 이 발생하여 view 가 해체되었기 때문입니다. 108 페이지의 Plan을 보시면 view가 해체되지 않고 남아 있습니다.

      dbms_xplan.display_cursor의 옵션에서 +outline 옵션을 추가하시면 오라클이 merge 힌트를 사용한 것을 볼 수 있을 것입니다. 이 힌트를 no_merge 로만 바꿔주시면 문제가 해결될 것입니다.

      이것은 옵티마이져의 실수인데 이때 튜너가 개입하여 더좋은 실행계획을 만들어 주어야 합니다.

      제가 일하고 있는곳의 프로젝트의 환경이 10g이므로 11.2 에서 직접 테스트해보지 못한점을 양해바랍니다.
      감사합니다.

    • Favicon of http://www.interpark.com BlogIcon 피아체 2010.04.30 15:33  댓글주소  수정/삭제

      잘 됩니다^^.
      NO_MERGE 힌트를 어디다 써야 할지를 모르겠네요..그냥 AND ROWNUM >= 0 으로 처리 했는데...
      암튼 책 너무 너무 잘 보고 있습니다.

  4. 타락천사 2010.05.11 11:41  댓글주소  수정/삭제  댓글쓰기

    P.120
    Subsumtion => subsumption 이 맞는거죠 ?
    영어 단어 찾다가 ㅡ_ㅡ;;
    네이버 영어 사전에 없는데...
    포섭 (subsumtion) 라는 의미가 있나보네요..
    다른분들도 찾을까 그냥 댓글 둡니다.

  5. 타락천사 2010.05.11 17:09  댓글주소  수정/삭제  댓글쓰기

    P.138
    2.18 부분이요
    CRSW 가 제목이나, 내용에서 언급되는 부분(P.141)은 RSW 로 언급되고 있습니다.
    ( 제목이나 내용을 통해서 보면, CRSW 가 맞는 것으로 생각되네요 )
    그리구 2.19 에서는 새로 추가된 URSW 를 소개 하고 있습니다.

    • Favicon of https://scidb.tistory.com BlogIcon extremedb 2010.05.11 17:22 신고  댓글주소  수정/삭제

      지적에 감사드립니다.
      해당 부분은 오타가 아닙니다.
      CRSW 와 URSW 모두 RSW로 묶을 수 있습니다.
      141 페이지 에 설명된 부분은 RSW 로 될 수밖에 없습니다. 왜냐하면 파라미터는 CRSW 와 URSW를 구분하지 않으므로 RSW라고 한것이고 11.2 부터 RSW라는 약어가 사용되고 세분화 되었다는 것을 이야기한 것이기 때문 입니다.

      감사합니다.

  6. 뽀로로 2010.07.14 02:18  댓글주소  수정/삭제  댓글쓰기

    책 p.194에 "Pivot 절을 Case + Group By로 변환하라" 라는 타이틀이요.
    혹시 Group By + Transpose 가 맞는 것은 아닌지요.
    문의 드립니다.

    • Favicon of https://scidb.tistory.com BlogIcon extremedb 2010.07.14 10:31 신고  댓글주소  수정/삭제

      안녕하세요. 뽀로로님
      문의 하신 제목은 파라미터가 pivot2 인 경우에 해당하는 것입니다.

      책의 제목은 pivot1 에 해당하는 것 입니다.
      Default로 적용할 경우 pivot1 이 적용되기 때문에 타이틀은 "Pivot 절을 Case + Group By로 변환하라" 이 맞습니다.

      하지만 pivot2 인 경우는 말씀하신것처럼 별도의 타이틀이 필요한데 책을 집필할 때에도 이부분에서 고민을 하였습니다. pivot1과 povot2를 별도의 장으로 만들고 각각의 타이틍을 달면 해결되기는 하는데 반대 의견이 많아 그러지 못햇습니다.

  7. 2010.08.26 11:43  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • Favicon of https://scidb.tistory.com BlogIcon extremedb 2010.08.26 11:12 신고  댓글주소  수정/삭제

      oraking님 반갑습니다.
      unique 인덱스가 존재해야 하는 이유는 답이 달라지기 때문입니다. unique 인덱스로 엑세스하면 단 한건만 나오므로 건수에 영향을 미치지 않습니다. 하지만 unique 인덱스가 아니라면 여러건이 나올 수 있으므로 건수가 달라지지요.

      가령 부서와 직원을 조인할때 부서에 해당하는 직원은 여러명일 수 있습니다. 직원쪽 테이블에 아우터 조인을 걸었고 select 절에도 직원쪽 컬럼을 사용하지 않았다고 하더라도 직원쪽 테이블은 제거할 수 없겠습니다. 건수가 달라지니까요.

      감사합니다.

  8. 2010.08.26 11:51  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • Favicon of https://scidb.tistory.com BlogIcon extremedb 2010.08.26 11:50 신고  댓글주소  수정/삭제

      옵타마이져는 답이달라지는 경우(건수가 달라지거나 값이다른 경우)는 아우터 쪽을 삭제하지 않습니다. 공통코드 쪽에 조인컬럼으로 unique 인덱스가 없으면 조인을 하여 결과건수가 같음을 보장하지 않기 때문에 아우터 쪽을 삭제할 수 없게된다는 뜻입니다. 77 페이지 를 실행해 보시기 바랍니다.

    • Favicon of https://scidb.tistory.com BlogIcon extremedb 2010.08.26 12:51 신고  댓글주소  수정/삭제

      아... 질문이 바뀌었네요.
      제대로 이해하셨습니다. 설명하신 대로 조인결과가 한건임을 보장해야 제거가 가능하다는 겁니다. unique 인덱스가 필수 겠지요.

  9. 2010.08.26 12:06  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • Favicon of https://scidb.tistory.com BlogIcon extremedb 2010.08.26 12:48 신고  댓글주소  수정/삭제

      Unique 인덱스가 존재해야 OE가 일어납니다. FK는 필요없습니다.

      77페이지는 je가 아니고 oe 입니다. oe는 11g 부터 가능하다고 밝히고 있고 fk 가 필요없다고 나와있습니다. 아마 다른 페이지를 보신듯 합니다. 페이지와 버젼확인 바랍니다.

      10g에서 없는 기능이 많으므로 테스트가 되지 않습니다. 효울적인 실습을 위하여 11g R2로 테스트 하시기 바랍니다.

      감사합니다.

  10. 무궁무진 2011.02.24 17:45  댓글주소  수정/삭제  댓글쓰기

    오타/오류 정정해서 책을 쭈욱 보다가 p170 10번째줄 "E1에 대하여 JESS가 수행되어" 부분에 JESS -> JESJ 오타인것 같아서 적어봅니다.

  11. 오수영 2011.08.09 11:00  댓글주소  수정/삭제  댓글쓰기

    105 Page
    3) Lateral View는 마치 스칼라 서브쿼리처럼 동작하므로 Sort Mege Join이나 hash Join을 사용할 수 없고

    에서 Sort Merge Join이 Sort Mege Join으로 표기 되었네요 ^^

  12. 최진수 2011.11.29 00:56  댓글주소  수정/삭제  댓글쓰기

    P 35에서 위의 SQL에 해당하는 SQL은 구절에서 위의 QB에 해당하는 SQL은 으로 변경되어야 할 것 같네요

  13. 이재현 2013.04.15 18:52  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 이재현입니다.

    한가지 궁금한게있습니다. ㅠ

    P.132 ) SSU 는 PART 2 Heuristic Query Transformation 이라고 되어있는데..

    해당 트레이스 파일에는.. p.135

    ***********************************************************
    Cost-Based Subquery Unnesting
    ***********************************************************
    SU: Unnestiong query ....

    이렇게 나오는데요.. 먼가좀 이상해요..ㅠㅠ

    설명좀 부탁드려도 될까요?? ㅠ