我只是想举个例子来解释Oracle中的NULL
如何导致'意外'行为,但我发现了一些我没想到的......
设置:
create table tabNull (val varchar2(10), descr varchar2(100));
insert into tabNull values (null, 'NULL VALUE');
insert into tabNull values ('A', 'ONE CHAR');
这给出了我的预期:
SQL> select * from tabNull T1 inner join tabNull T2 using(val);
VAL DESCR DESCR
---------- -------------------- --------------------
A ONE CHAR ONE CHAR
如果删除表别名,我会得到:
SQL> select * from tabNull inner join tabNull using(val);
VAL DESCR DESCR
---------- -------------------- --------------------
A ONE CHAR ONE CHAR
A ONE CHAR ONE CHAR
这对我来说非常令人惊讶。
可以在两个查询的执行计划中找到原因;使用表别名,Oracle进行HASH JOIN,然后检查T1.val = T2.val
:
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 118 | 7 (15)| 00:00:01 |
|* 1 | HASH JOIN | | 1 | 118 | 7 (15)| 00:00:01 |
| 2 | TABLE ACCESS FULL| TABNULL | 2 | 118 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| TABNULL | 2 | 118 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("T1"."VAL"="T2"."VAL")
如果没有别名,它首先会过滤掉一个表的非空值,因此只选择一行,然后它会产生第二次出现的CARTESIAN,从而得到两行;即使它是正确的,我也期待笛卡尔的结果,但我没有DESCR ='NULL VALUE'的行。
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 118 | 6 (0)| 00:00:01 |
| 1 | MERGE JOIN CARTESIAN| | 2 | 118 | 6 (0)| 00:00:01 |
|* 2 | TABLE ACCESS FULL | TABNULL | 1 | 59 | 3 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 2 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL | TABNULL | 2 | | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("TABNULL"."VAL" IS NOT NULL)
这是否正确/预期?笛卡尔的结果值是否比返回的行数更奇怪?我是否误解了这些计划,或遗漏了一些我看不到的大事?
答案 0 :(得分:1)
根据http://docs.oracle.com/javadb/10.10.1.2/ref/rrefsqljusing.html
using(val)
在此处翻译为ON tabnull.val=tabnull.val
所以
select tabNull.*, tabNull.descr from tabNull inner join tabNull
on tabNull.val = tabNull.val;
接下来要构建一个计划Oracle必须[虚拟]为每个JOIN成员分配不同的别名,但没有理由在SELECT和ON中的任何位置使用第二个别名。所以
select t1.*, t1.descr from tabNull t1 inner join tabNull t2
on t1.val = t1.val;
计划
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 28 | 4 (0)| 00:00:01 |
| 1 | MERGE JOIN CARTESIAN| | 2 | 28 | 4 (0)| 00:00:01 |
|* 2 | TABLE ACCESS FULL | TABNULL | 1 | 14 | 2 (0)| 00:00:01 |
| 3 | BUFFER SORT | | 2 | | 2 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL | TABNULL | 2 | | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("T1"."VAL" IS NOT NULL)
答案 1 :(得分:0)
编辑:我在下面说法语是非法的;进一步思考,我本人的BS,我不知道一个事实(我不能指出语言定义中的自我连接需要别名)。我仍然相信下面的解释可能是正确的,无论是针对" bug"或者对于"未定义的行为"我在下面提到。
*
语法是非法的(你知道 - 你只是想知道会发生什么,如果你能理解输出)。我同意jarlh你应该收到错误信息。显然,甲骨文并没有这样编码。
由于这不是有效的语法,因此您所看到的内容不能称为错误(因此我不同意Nick的评论)。行为是"未定义" - 当您使用Oracle语言定义不支持的语法时,您可能会得到任何类型的疯狂结果,Oracle对此不承担任何责任。
好的,有了这个,你有什么解释吗?我相信这确实是笛卡尔式的加入,而不是尼克建议的联盟。
让我们把自己放在优化者的鞋子里。它看到FROM列表中的第一个表,它扫描它,到目前为止一直很好。
然后它读取第二个表,它有一个列表,如下所示:
tabNULL.val, tabNULL.descr, tabNULL.val, tabNULL.descr
加入条件为tabNULL.val = tabNULL.val
优化器很笨,不聪明。它与你不同,在这一点上并没有意识到tabNULL
意味着代表桌子的两个不同的化身。它认为等式两边的tabNULL.val
是同一个值,它们都指的是第一个"化身"的表。失败的唯一情况是tabNULL.val
为NULL,因此它使用tabNULL.val IS NOT NULL
子句重写查询。
仅检查FIRST表的tabNULL.val IS NOT NULL
;优化器没有"知道" tabNULL.val
再次出现在列表中,它可能具有不同的含义!然后连接发生;此时没有其他条件,因此表的第二个化身中的两个行将在连接中产生行,对于第一个表中的A, ONE CHAR
。
然后,在投影中,将再次仅读取FIRST tabNULL.val
并将在输出中填充BOTH列。您要求查询引擎返回值tabNULL.val
两次,并且在您的脑海中它来自不同的地方,但只有一个内存位置标记为tabNULL.val
,并且它存储来自第一桌。
当然,很少有人确切知道优化器和查询引擎的作用,但在这种情况下,我认为这是一个非常安全的猜测。
答案 2 :(得分:0)
USING
关键字对我来说是新的,但根据我所读到的,它只是一种简化SQL连接语法的新方法。 (见Oracle USING Keyword)
select * from tabNull T1 inner join tabNull T2 using(val);
相当于:
select * from tabNull T1 inner join tabNull T2 on T1.val = T2.val;
select * from tabNull inner join tabNull using(val);
相当于:
select * from tabNull inner join tabNull on tabNull.val = tabNull.val;
问题是在第二个查询中,联接tabNull.val = tabNull.val
中的表名不是唯一的
这是一种错误的语法,如果使用了传统的连接语法,则会导致错误
我最好的猜测是Oracle在两个表上执行了完整的交叉产品(将所有行加倍),然后消除了空值,因为USING
必须使用equijoins(即等于“=
”)和null
不等于任何事情。
答案 3 :(得分:0)
对不起,我认为这不是一个真正的答案。在您的帖子中,这主要只是对此的评论/回复:
笛卡尔的结果值是否比返回的行数更奇怪?
计划的每一步都有一个“投影”,即从步骤输出的列/表达式列表。发生的事情是,相同的别名导致Oracle的投影将应该是两列的投影组合成一列。
如果您在示例中使用两个单独的表并添加一对唯一命名的列以查看正在发生的事情,则更容易看到,如下所示:
create table tabNull1 (val varchar2(10), descr varchar2(100), t1_real_descr varchar2(100) );
insert into tabNull1 values (null, 'T1-NULL VALUE', 'T1-NULL VALUE');
insert into tabNull1 values ('A', 'T1-ONE CHAR', 'T1-ONE CHAR');
create table tabNull2 (val varchar2(10), descr varchar2(100), t2_real_descr varchar2(100) );
insert into tabNull2 values (null, 'T2-NULL VALUE', 'T2-NULL VALUE');
insert into tabNull2 values ('A', 'T2-ONE CHAR', 'T2-ONE CHAR');
select * from tabNull1 t inner join tabNull2 t using(val);
VAL DESCR T1_REAL_DESCR DESCR_1 T2_REAL_DESCR
------ ---------------- ----------------- ------------- -----------------
A T2-ONE CHAR T1-NULL VALUE T2-ONE CHAR T2-ONE CHAR
A T2-ONE CHAR T1-ONE CHAR T2-ONE CHAR T2-ONE CHAR
如您所见,您关于笛卡尔联合的理论是正确的。