Подтвердить что ты не робот

Является ли это ошибкой в ​​Oracle при подключении таблицы к представлению, которое зависит от таблицы

Я нашел то, что, по моему мнению, является ошибкой в ​​Oracle, но мне интересно, есть ли что-то задокументированное, что я пропустил.

скрипки: Oracle: http://sqlfiddle.com/#!4/43c19/2 SQL Server: http://sqlfiddle.com/#!3/ddc49/1 MySql: http://sqlfiddle.com/#!2/43c195/1

В основном у меня есть основная таблица, в которой я оставил присоединение к вторичной таблице. Затем я оставил соединение на виду. Если я укажу в соединении с представлением, что только хочу присоединиться, когда столбец во вторичной таблице не равен нулю, я получаю неожиданные результаты. Это лучше всего объяснить, показывая запрос:

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 отличается. В схеме есть три таблицы и два вида. Представления определяются следующим образом:

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;

В Oracle я обнаружил, что если представление содержит ссылку на SecondaryTable, тогда я получаю только строки из MainTable, которые имеют совпадение в Secondary table. Мне кажется, что Oracle каким-то образом вставляет код представления, так что одна из строк опускается.

Я думаю, что если MainTable имеет три строки, то выполнение двух левых объединений должно всегда возвращать три строки по крайней мере плюс любые результаты из соединения. Однако в приведенном примере это не так.

Я знаю, что SecondaryTable.KeyValue IS NOT NULL избыточно, поскольку вторая половина предложения не будет истинна, если значение равно null, но я пытаюсь переработать запрос, чтобы помочь оптимизатору разработать лучший план.

Полное создание script для запуска примера:

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;
4b9b3361

Ответ 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"(+))