2015-01-12 11 views
12

Ho trovato quello che penso sia un bug in Oracle, ma mi chiedo se c'è qualcosa di documentato che ho perso.Si tratta di un bug in Oracle quando si unisce una tabella a una vista che dipende dalla tabella

Fiddles: Oracle: http://sqlfiddle.com/#!4/43c19/2 SQL Server: http://sqlfiddle.com/#!3/ddc49/1 MySql: http://sqlfiddle.com/#!2/43c195/1

Fondamentalmente ho una tabella principale che ho lasciato unirsi su una tabella secondaria. Poi ho lasciato join su una vista. Se specificherò nella join la vista che voglio unire solo quando una colonna nella tabella secondaria non è nulla, ottengo risultati imprevisti. La spiegazione migliore è la seguente:

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) 

Vedere di seguito gli script di creazione per testarlo da soli. In SQL Server e MySql ottengo gli stessi risultati, tuttavia Oracle è diverso. Ci sono tre tabelle e due viste nello schema. I punti di vista sono definiti come segue:

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; 

In Oracle, ho trovato che se la vista contiene un riferimento a SecondaryTable, quindi ho solo righe da MainTable che hanno una corrispondenza nella tabella secondaria. Mi sembra che Oracle stia in qualche modo incoraggiando il codice della vista in modo tale che una delle righe sia omessa.

Penso che se MainTable ha tre righe, quindi fare due join di sinistra dovrebbe sempre restituire almeno le tre righe, più eventuali risultati del join. Comunque nell'esempio dato questo non è il caso.

So che lo SecondaryTable.KeyValue IS NOT NULL è ridondante poiché la seconda metà della clausola non sarebbe vera se il valore è nullo, ma ho cercato di rielaborare una query per aiutare l'ottimizzatore a trovare un piano migliore.

Lo script completo creazione per eseguire l'esempio è:

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 ottiene lo stesso risultato di MySQL. – jarlh

+0

Quindi quale motore DB è questo: MySQL, SQL Server o Oracle? Hai tutti e tre i tag –

+0

Sono d'accordo che questo deve essere un bug. L'ho provato con Oracle 10.2 e ho ottenuto lo stesso risultato con l'unica riga mancante. MySQL e SQL Server funzionano correttamente, Oracle no. –

risposta

1

Se si esegue un spiegare piano per la query, si può vedere che Oracle sta trasformando la query inlining la vista, e, per qualche ragione , sta eseguendo un inner join alla linea 2, piuttosto che un left-outer.

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") 

Una soluzione per questo problema è utilizzare il suggerimento 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); 

Questo produce il risultato atteso:

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

Confrontare il piano di query per la query accennato. Qui vediamo un join sinistro-esterno alla riga 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"(+)) 
Problemi correlati