我发现我认为是 Oracle 中的一个错误,但我想知道是否有我遗漏的文档记录。
fiddle : 甲骨文:http://sqlfiddle.com/#!4/43c19/2 SQL Server:http://sqlfiddle.com/#!3/ddc49/1 MySql:http://sqlfiddle.com/#!2/43c195/1
基本上我有一个主表,我将其加入到辅助表中。然后我离开了一个 View 。如果我在 View 的连接中指定我只想在辅助表中的列不为空时加入,我会得到意想不到的结果。最好通过显示查询来解释这一点:
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 不同。架构中有三个表和两个 View 。 View 定义如下:
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 中,我发现如果 View 包含对 SecondaryTable 的引用,那么我只会从 MainTable 中获取与 Secondary 表匹配的行。在我看来,Oracle 正在以某种方式内联 View 代码,以便省略其中一行。
我认为,如果 MainTable 有三行,那么对其进行两次左连接应该总是至少返回三行,加上连接的任何结果。然而,在给出的示例中,情况并非如此。
我知道 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;
最佳答案
如果您对查询运行解释计划,您可以看到 Oracle 正在通过内联 View 来转换查询,并且出于某种原因,它在第 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"(+))
关于mysql - 将表连接到依赖于表的 View 时,这是 Oracle 中的错误吗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27903303/