Ответ 1
Если вы запустите план объяснения по запросу, вы увидите, что Oracle преобразует запрос, вставляя представление, и по какой-то причине он выполняет внутреннее соединение в строке 2, а не влево-внешнем.
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"(+))