目 录CONTENT

文章目录

【ORACLE】你以为的真的是你以为的么?--ERROR: Operator "(+)" is not allowed used with "OR" together

DarkAthena
2025-05-20 / 0 评论 / 0 点赞 / 11 阅读 / 0 字

【ORACLE】你以为的真的是你以为的么?--ERROR: Operator "(+)" is not allowed used with "OR" together

背景

在ORACLE迁移到GaussDB/openGauss及openGauss的某些商业发行版时,可能会遇到这样一个报错

ERROR: Operator "(+)" is not allowed used with "OR" together

直译就是操作符(+)不允许和"OR"一起使用,这是openGauss特意加的限制么?

分析

如下例:

create table test_left_join_a (a number,b number);
create table test_left_join_b (a number,b number);
insert into test_left_join_a values (1,2);
commit;
select * from test_left_join_a a, test_left_join_b b where a.a=b.a(+) and (b.b(+) =1 or b.b(+) is null);

ERROR: Operator "(+)" is not allowed used with "OR" together

而这条SQL在ORACLE中是不会报错的,
于是乎,猜测当初GaussDB的产品经理或者研发是不是觉得这个功能用得比较少,而且可能有风险,就加了报错来限制?
于是乎,可能后续其他国产数据库的产品经理就会向内核研发提出一个需求,要支持(+)和OR一起用。

但是,如果我告诉你,其实ORACLE里也有个这样的报错,ORA-01719: outer join operator (+) not allowed in operand of OR or IN (ORA-01719: OR 或 IN 操作数中不允许外部联接运算符 (+)),你会不会一阵眩晕?

看看10053中的执行计划(explain plan看不准)

----- Current SQL Statement for this session (sql_id=gb6uskq2zskx3) -----
select * from test_left_join_a a, test_left_join_b b where a.a=b.a(+) and (b.b(+) =1 or b.b(+) is null)
sql_text_length=104
sql=select * from test_left_join_a a, test_left_join_b b where a.a=b.a(+) and (b.b(+) =1 or b.b(+) is null)
----- Explain Plan Dump -----
----- Plan Table -----
 
============
Plan Table
============
----------------------------------------------+-----------------------------------+
| Id  | Operation           | Name            | Rows  | Bytes | Cost  | Time      |
----------------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT    |                 |       |       |     5 |           |
| 1   |  HASH JOIN OUTER    |                 |     1 |    52 |     5 |  00:00:01 |
| 2   |   TABLE ACCESS FULL | TEST_LEFT_JOIN_A|     1 |    26 |     3 |  00:00:01 |
| 3   |   TABLE ACCESS FULL | TEST_LEFT_JOIN_B|     1 |    26 |     2 |  00:00:01 |
----------------------------------------------+-----------------------------------+
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
 1 - SEL$1                
 2 - SEL$1                / "A"@"SEL$1"
 3 - SEL$1                / "B"@"SEL$1"
-------------------------------------------------------------
Predicate Information:
----------------------
1 - access("A"."A"="B"."A")
3 - filter(("B"."B" IS NULL OR "B"."B"=1))
 

这里可以发现filter是在对B表的访问中加的,而且对B表的(+)不见了,也就是说,原SQL可以等价于以下这条SQL

select * from test_left_join_a a, (select * from test_left_join_b b where (b.b =1 or b.b is null)) b where a.a=b.a(+);

而改写成这样的SQL,在openGauss中执行是不会报错的!

为什么不是原SQL直接去掉(+)? 因为我跟踪了执行计划发现不一样

----- Current SQL Statement for this session (sql_id=d534s5m06f6f6) -----
select * from test_left_join_a a, test_left_join_b b where a.a=b.a(+) and (b.b =1 or b.b is null)
sql_text_length=98
sql=select * from test_left_join_a a, test_left_join_b b where a.a=b.a(+) and (b.b =1 or b.b is null)
----- Explain Plan Dump -----
----- Plan Table -----
 
============
Plan Table
============
-----------------------------------------------+-----------------------------------+
| Id  | Operation            | Name            | Rows  | Bytes | Cost  | Time      |
-----------------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT     |                 |       |       |     5 |           |
| 1   |  FILTER              |                 |       |       |       |           |
| 2   |   HASH JOIN OUTER    |                 |     1 |    52 |     5 |  00:00:01 |
| 3   |    TABLE ACCESS FULL | TEST_LEFT_JOIN_A|     1 |    26 |     3 |  00:00:01 |
| 4   |    TABLE ACCESS FULL | TEST_LEFT_JOIN_B|     1 |    26 |     2 |  00:00:01 |
-----------------------------------------------+-----------------------------------+
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
 1 - SEL$1                
 3 - SEL$1                / "A"@"SEL$1"
 4 - SEL$1                / "B"@"SEL$1"
-------------------------------------------------------------
Predicate Information:
----------------------
1 - filter(("B"."B"=1 OR "B"."B" IS NULL))
2 - access("A"."A"="B"."A")

我们回头看下ORACLE官方文档对ORA-01719的描述
https://docs.oracle.com/en/error-help/db/ora-01719/?r=19c

ORA-01719
outer join operator (+) not allowed in operand of OR or IN
Cause
An outer join appears in an or clause.
Action
If A and B are predicates, to get the effect of (A(+) or B), try (select where (A(+) and not B)) union all (select where (B)).

如果 A 和 B 是谓词,要得到(A(+) 或 B)的效果,尝试使用(select where (A(+) and not B)) union all (select where (B))。

光从报错信息和原因来看,(b.b(+) =1 or b.b(+) is null)是应该要报错的,但是处理方式中描述则是(A(+) or B)的场景,即两个条件,一个带(+)一个不带(+)才会触发。
于是在ORACLE中尝试执行以下SQL

SQL> select * from test_left_join_a a, test_left_join_b b where a.a=b.a(+) and (b.b(+) =1 or b.b is null);
select * from test_left_join_a a, test_left_join_b b where a.a=b.a(+) and (b.b(+) =1 or b.b is null)
                                                                                  *
第 1 行出现错误:
ORA-01719: OR 或 IN 操作数中不允许外部联接运算符 (+)

果然报错了。

也就是说,ORACLE的报错信息不严谨,它并不是绝对不允许(+)和or一起用,而是只限制了部分场景。而且这个校验还得发生在SQL优化器处理之后。但是openGauss中是在SQL解析器阶段就进行了校验,导致了ORACLE中不报错的SQL在openGauss中会报错。

目前openGauss系中,仅MogDB 5.2VastBase v3.0 build8支持(+)和or一起使用。另外,非openGauss/PG系的yashan也不支持。

总结

其实类似这样的情况,不止这一个场景,ORACLE有很多语法限制不能使用,但是很多情况下是对SQL优化器处理后的校验。但我们知道,ORACLE的同一个SQL是可能产生不同的执行计划的,而且不同的SQL也可能产生相同的执行计划,ORACLE在优化器处理后进行校验,那么就有可能出现对同一条SQL有时候报错有时候不报错。而openGauss在优化器处理前进行校验,虽然让某些能有正确执行路径的SQL也报错了,但不会出现一会儿报错一会儿不报错的情况。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论