将 table 加入依赖于 table 的视图时,这是 Oracle 中的错误吗
Is this a bug in Oracle when joining a table to a view that depends on the table
我发现了我认为是 Oracle 中的错误,但我想知道是否有我遗漏的记录。
小提琴:
甲骨文:http://sqlfiddle.com/#!4/43c19/2
SQL 服务器:http://sqlfiddle.com/#!3/ddc49/1
MySql: http://sqlfiddle.com/#!2/43c195/1
基本上我有一个主要的 table,我离开它加入了次要的 table。然后我离开加入一个视图。如果我在加入视图时指定我只想在辅助 table 中的列不为 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 服务器和 MySql 我得到相同的结果,但是 Oracle 是不同的。架构中有三个 table 和两个视图。视图定义如下:
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 有三行,那么对它进行两次左连接应该总是 return 至少三行,加上连接的任何结果。然而,在给出的示例中,情况并非如此。
我知道 SecondaryTable.KeyValue IS NOT NULL
是多余的,因为如果值为 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;
如果您 运行 查询的解释计划,您可以看到 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"(+))
我发现了我认为是 Oracle 中的错误,但我想知道是否有我遗漏的记录。
小提琴: 甲骨文:http://sqlfiddle.com/#!4/43c19/2 SQL 服务器:http://sqlfiddle.com/#!3/ddc49/1 MySql: http://sqlfiddle.com/#!2/43c195/1
基本上我有一个主要的 table,我离开它加入了次要的 table。然后我离开加入一个视图。如果我在加入视图时指定我只想在辅助 table 中的列不为 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 服务器和 MySql 我得到相同的结果,但是 Oracle 是不同的。架构中有三个 table 和两个视图。视图定义如下:
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 有三行,那么对它进行两次左连接应该总是 return 至少三行,加上连接的任何结果。然而,在给出的示例中,情况并非如此。
我知道 SecondaryTable.KeyValue IS NOT NULL
是多余的,因为如果值为 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;
如果您 运行 查询的解释计划,您可以看到 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"(+))