2015-01-12 4 views
12

Oracle에서 버그라고 생각되는 것을 발견했습니다. 그러나 내가 놓친 문서가 있는지 궁금합니다.테이블에 종속 된 뷰에 테이블을 결합 할 때 Oracle에서의 버그입니까?

바이올린 : 오라클 : http://sqlfiddle.com/#!4/43c19/2 SQL 서버 : http://sqlfiddle.com/#!3/ddc49/1 MySQL의 : http://sqlfiddle.com/#!2/43c195/1

기본적으로 내가 보조 테이블에 조인 왼쪽 기본 테이블이있다. 그 다음 나는 전망에 합류했다. 보조 테이블의 열이 null이 아닐 때 조인하려는 뷰에 조인을 지정하면 예기치 않은 결과가 발생합니다. 이것은 쿼리를 보여줌으로써 가장 잘 설명됩니다 :

SELECT 
    1, 
    MainTable.* 
FROM 
    MainTable 
    LEFT JOIN SecondaryTable ON MainTable.KeyColumn = SecondaryTable.KeyColumn 
    LEFT JOIN ViewWithoutSecondary ON ((SecondaryTable.KeyColumn IS NOT NULL) AND SecondaryTable.KeyColumn = ViewWithoutSecondary.KeyColumn) 
UNION ALL 
SELECT 
    2, 
    MainTable.* 
FROM 
    MainTable 
    LEFT JOIN SecondaryTable ON MainTable.KeyColumn = SecondaryTable.KeyColumn 
    LEFT JOIN ViewWithSecondary ON ((SecondaryTable.KeyColumn IS NOT NULL) AND SecondaryTable.KeyColumn = ViewWithSecondary.KeyColumn) 

직접 테스트 스크립트를 작성하려면 아래를 참조하십시오. SQL Server와 MySql에서 동일한 결과를 얻지 만 Oracle은 다릅니다. 스키마에는 3 개의 테이블과 2 개의 뷰가 있습니다.

오라클에서
CREATE VIEW ViewWithoutSecondary 
AS 
SELECT 
    TertiaryTable.KeyColumn, 
    TertiaryValue + 1 ViewValue 
FROM 
    TertiaryTable 

CREATE VIEW ViewWithSecondary 
AS 
SELECT 
    SecondaryTable.KeyColumn, 
    TertiaryValue + 1 ViewValue 
FROM 
    SecondaryTable 
    LEFT JOIN TertiaryTable ON SecondaryTable.KeyColumn = TertiaryTable.KeyColumn; 

, 내가보기 SecondaryTable에 대한 참조를 포함하는 경우, 그때는 보조 테이블에서 일치를 MainTable에서 행을 얻을 것으로 나타났습니다 다음과 같이 뷰가 정의되어 있습니다. 오라클이 뷰 코드를 어떤 식 으로든 인라인하여 행 중 하나가 생략 된 것처럼 보입니다.

MainTable에 세 개의 행이 있다면 두 개의 왼쪽 조인을 수행 할 때 적어도 세 개의 행과 항상 조인의 결과를 반환해야한다고 생각합니다. 그러나 주어진 예에서는 그렇지 않습니다.

값이 null 인 경우 절의 후반부가 true가 아니기 때문에 SecondaryTable.KeyValue IS NOT NULL이 중복되는 것을 알고 있지만 최적화 프로그램이 더 좋은 계획을 세울 수 있도록 쿼리를 다시 작성하려고했습니다.

예제를 실행하기 위해 전체 작성 스크립트는 다음과 같습니다 당신이이 쿼리 계획을 설명 실행하면

CREATE TABLE MainTable 
(
    KeyColumn varchar(32), 
    ValueColumn varchar(32) 
); 

INSERT INTO MainTable VALUES ('123', 'abc'); 
INSERT INTO MainTable VALUES ('456', 'def'); 
INSERT INTO MainTable VALUES ('789', 'ghi'); 

CREATE TABLE SecondaryTable 
(
    KeyColumn varchar(32), 
    SecondaryValue integer 
); 

INSERT INTO SecondaryTable VALUES ('123', 1); 
INSERT INTO SecondaryTable VALUES ('456', 2); 

CREATE TABLE TertiaryTable 
(
    KeyColumn varchar(32), 
    TertiaryValue integer 
); 

INSERT INTO TertiaryTable VALUES ('123', 1); 

CREATE VIEW ViewWithoutSecondary 
AS 
SELECT 
    TertiaryTable.KeyColumn, 
    TertiaryValue + 1 ViewValue 
FROM 
    TertiaryTable; 

CREATE VIEW ViewWithSecondary 
AS 
SELECT 
    SecondaryTable.KeyColumn, 
    TertiaryValue + 1 ViewValue 
FROM 
    SecondaryTable 
    LEFT JOIN TertiaryTable ON SecondaryTable.KeyColumn = TertiaryTable.KeyColumn; 
+1

Mimer SQL은 MySQL과 동일한 결과를 얻습니다. – jarlh

+0

그래서 DB 엔진은 MySQL, SQL Server 또는 Oracle입니까? 세 개의 태그가 모두 있습니다 –

+0

나는 이것이 버그 여야합니다 동의합니다. 필자는 Oracle 10.2를 사용해 보았습니다. 한 행 누락과 같은 결과를 얻었습니다. MySQL과 SQL Server는 올바르게 작동하지만 Oracle은 그렇지 않습니다. –

답변

1

, 어떤 이유로, 오라클은보기를 인라인으로 쿼리를 변환되는 것을 참조 할 수 있습니다 , 그것은 왼쪽 - 바깥 쪽이 아닌 두 번째 줄에서 내부 조인을 수행합니다.

explain plan 
SET statement_id = 'no-hint' FOR 
SELECT 
    MainTable.* 
FROM 
    MainTable 
    LEFT JOIN SecondaryTable ON MainTable.KeyColumn = SecondaryTable.KeyColumn 
    LEFT JOIN ViewWithSecondary ON ((SecondaryTable.KeyColumn IS NOT NULL) AND SecondaryTable.KeyColumn = ViewWithSecondary.KeyColumn); 

SELECT PLAN_TABLE_OUTPUT 
    FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'no-hint','TYPICAL')); 


---------------------------------------------------------------------------------------- 
| Id | Operation    | Name   | Rows | Bytes | Cost (%CPU)| Time  | 
---------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT  |    |  2 | 108 | 20 (10)| 00:00:01 | 
| 1 | NESTED LOOPS OUTER |    |  2 | 108 | 20 (10)| 00:00:01 | 
|* 2 | HASH JOIN   |    |  2 | 108 |  7 (15)| 00:00:01 | 
| 3 | TABLE ACCESS FULL | SECONDARYTABLE |  2 | 36 |  3 (0)| 00:00:01 | 
| 4 | TABLE ACCESS FULL | MAINTABLE  |  3 | 108 |  3 (0)| 00:00:01 | 
| 5 | VIEW    |    |  1 |  |  7 (15)| 00:00:01 | 
|* 6 | FILTER    |    |  |  |   |   | 
|* 7 |  HASH JOIN OUTER |    |  1 | 36 |  7 (15)| 00:00:01 | 
|* 8 |  TABLE ACCESS FULL| SECONDARYTABLE |  1 | 18 |  3 (0)| 00:00:01 | 
| 9 |  TABLE ACCESS FULL| TERTIARYTABLE |  1 | 18 |  3 (0)| 00:00:01 | 
---------------------------------------------------------------------------------------- 

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

    2 - access("MAINTABLE"."KEYCOLUMN"="SECONDARYTABLE"."KEYCOLUMN") 
    6 - filter("SECONDARYTABLE"."KEYCOLUMN" IS NOT NULL) 
    7 - access("SECONDARYTABLE"."KEYCOLUMN"="TERTIARYTABLE"."KEYCOLUMN"(+)) 
    8 - filter("SECONDARYTABLE"."KEYCOLUMN"="SECONDARYTABLE"."KEYCOLUMN") 

이 문제의 해결 방법은 NO_MERGE 힌트를 사용하는 것입니다.

SELECT /*+ NO_MERGE(ViewWithSecondary) */ 
    MainTable.* 
FROM 
    MainTable 
    LEFT JOIN SecondaryTable ON MainTable.KeyColumn = SecondaryTable.KeyColumn 
    LEFT JOIN ViewWithSecondary ON ((SecondaryTable.KeyColumn IS NOT NULL) AND SecondaryTable.KeyColumn = ViewWithSecondary.KeyColumn); 

이 예상되는 결과를 생성 :

KEYCOLUMN      VALUECOLUMN      
-------------------------------- -------------------------------- 
123        abc        
456        def        
789        ghi 

는 암시 쿼리에 대한 쿼리 계획을 비교. 여기에 2 행의 왼쪽 외부 조인이 있습니다.

explain plan 
SET statement_id = 'with-hint' FOR 
SELECT /*+ NO_MERGE(ViewWithSecondary) */ 
    MainTable.* 
FROM 
    MainTable 
    LEFT JOIN SecondaryTable ON MainTable.KeyColumn = SecondaryTable.KeyColumn 
    LEFT JOIN ViewWithSecondary ON ((SecondaryTable.KeyColumn IS NOT NULL) AND SecondaryTable.KeyColumn = ViewWithSecondary.KeyColumn); 

SELECT PLAN_TABLE_OUTPUT 
    FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'with-hint','TYPICAL')); 

-------------------------------------------------------------------------------------------- 
| Id | Operation    | Name    | Rows | Bytes | Cost (%CPU)| Time  | 
-------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT  |     |  6 | 324 | 26 (8)| 00:00:01 | 
| 1 | NESTED LOOPS OUTER |     |  6 | 324 | 26 (8)| 00:00:01 | 
|* 2 | HASH JOIN OUTER  |     |  3 | 162 |  7 (15)| 00:00:01 | 
| 3 | TABLE ACCESS FULL | MAINTABLE   |  3 | 108 |  3 (0)| 00:00:01 | 
| 4 | TABLE ACCESS FULL | SECONDARYTABLE |  2 | 36 |  3 (0)| 00:00:01 | 
| 5 | VIEW     |     |  2 |  |  7 (15)| 00:00:01 | 
|* 6 | FILTER    |     |  |  |   |   | 
|* 7 |  VIEW    | VIEWWITHSECONDARY |  2 | 36 |  7 (15)| 00:00:01 | 
|* 8 |  HASH JOIN OUTER |     |  2 | 72 |  7 (15)| 00:00:01 | 
| 9 |  TABLE ACCESS FULL| SECONDARYTABLE |  2 | 36 |  3 (0)| 00:00:01 | 
| 10 |  TABLE ACCESS FULL| TERTIARYTABLE  |  1 | 18 |  3 (0)| 00:00:01 | 
-------------------------------------------------------------------------------------------- 

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

    2 - access("MAINTABLE"."KEYCOLUMN"="SECONDARYTABLE"."KEYCOLUMN"(+)) 
    6 - filter("SECONDARYTABLE"."KEYCOLUMN" IS NOT NULL) 
    7 - filter("SECONDARYTABLE"."KEYCOLUMN"="VIEWWITHSECONDARY"."KEYCOLUMN") 
    8 - access("SECONDARYTABLE"."KEYCOLUMN"="TERTIARYTABLE"."KEYCOLUMN"(+)) 
관련 문제