Oracle(+)操作符使用指南

在进行数据库迁移或兼容性开发时,您可能会遇到Oracle特有的(+)外连接语法。这种语法在WHERE子句中通过在列名后添加(+)符号来表示外连接,与标准的LEFT JOINRIGHT JOIN语法不同。为了帮助您理解和使用这种传统语法,PolarDB PostgreSQL版(兼容Oracle)提供了对(+)操作符的兼容支持,确保既有代码能够平滑迁移和运行。

功能简介

(+)操作符是Oracle数据库中用于实现外连接(Outer Join)的一种传统语法。它通过在WHERE子句的连接条件中,将(+)符号放置在需要补足NULL值的表的列名一侧,来定义左外连接或右外连接。

  • 左外连接(LEFT OUTER JOIN)
    (+)放在右表(需要补NULL的表)的连接列上。

    SELECT columns FROM table1, table2 WHERE table1.column = table2.column(+);

    这等效于标准的SQL语法:

    SELECT columns FROM table1 LEFT JOIN table2 ON table1.column = table2.column;
  • 右外连接(RIGHT OUTER JOIN)
    (+)放在左表(需要补NULL的表)的连接列上。

    SELECT columns FROM table1, table2 WHERE table1.column(+) = table2.column;

    这等效于标准的SQL语法:

    SELECT columns FROM table1 RIGHT JOIN table2 ON table1.column = table2.column;

注意事项

  • (+)操作符只能出现在WHERE子句中,不能出现在ON子句中。

  • 一个连接条件中,(+)操作符只能出现在等号的一侧。在一个查询中,同一个表的多个连接条件需一致地使用(+)

  • 包含(+)的连接条件不能与其他条件通过OR逻辑运算符组合。

  • 虽然PolarDB PostgreSQL版(兼容Oracle)支持(+)语法,但为了更好的可读性和跨数据库兼容性,建议在新项目中优先使用标准的LEFT JOINRIGHT JOIN语法。

使用示例

以下示例将演示(+)操作符的常见用法。

准备数据

在执行示例前,请先连接到您的PolarDB集群并创建以下测试表。

-- 创建员工表
CREATE TABLE emp (
    empno  NUMBER(4) PRIMARY KEY,
    ename  VARCHAR2(20),
    deptno NUMBER(2),
    sal    NUMBER(7,2)
);

-- 创建部门表
CREATE TABLE dept (
    deptno NUMBER(2) PRIMARY KEY,
    dname  VARCHAR2(20),
    loc    VARCHAR2(20)
);

-- 插入部门数据
INSERT INTO dept VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO dept VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO dept VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO dept VALUES (40, 'OPERATIONS', 'BOSTON'); -- 这是一个没有员工的部门

-- 插入员工数据
INSERT INTO emp VALUES (7369, 'SMITH', 20, 800);
INSERT INTO emp VALUES (7499, 'ALLEN', 30, 1600);
INSERT INTO emp VALUES (7782, 'CLARK', 10, 2450);
INSERT INTO emp VALUES (7839, 'KING', 10, 5000);
INSERT INTO emp VALUES (8000, 'TEMP', NULL, 1000); -- 这是一个没有部门的员工

COMMIT;

示例1:实现左外连接

查询所有员工及其部门信息。即使某些员工没有分配部门(即deptnoNULL),也需要将这些员工包含在结果中。

-- 使用(+)实现左外连接,(+)放在右表(dept)的列上
SELECT e.ename, e.sal, d.dname, d.loc
FROM emp e, dept d
WHERE e.deptno = d.deptno(+)
ORDER BY e.empno;

返回结果:

 ename | sal  |   dname    |   loc    
-------+------+------------+----------
 SMITH |  800 | RESEARCH   | DALLAS
 ALLEN | 1600 | SALES      | CHICAGO
 CLARK | 2450 | ACCOUNTING | NEW YORK
 KING  | 5000 | ACCOUNTING | NEW YORK
 TEMP  | 1000 |            | 
(5 rows)

结果说明:
查询结果会包含所有emp表中的员工。对于名为TEMP的员工,由于其deptnoNULL,无法在dept表中找到匹配项,因此对应的d.dnamed.loc列将显示为NULL

示例2:实现右外连接

查询所有部门及其员工信息。即使某些部门下没有员工,也需要将这些部门包含在结果中。

-- 使用(+)实现右外连接,(+)放在左表(emp)的列上
SELECT e.ename, e.sal, d.dname, d.loc
FROM emp e, dept d
WHERE e.deptno(+) = d.deptno
ORDER BY d.deptno, e.ename;

返回结果:

 ename | sal  |   dname    |   loc    
-------+------+------------+----------
 CLARK | 2450 | ACCOUNTING | NEW YORK
 KING  | 5000 | ACCOUNTING | NEW YORK
 SMITH |  800 | RESEARCH   | DALLAS
 ALLEN | 1600 | SALES      | CHICAGO
       |      | OPERATIONS | BOSTON
(5 rows)

结果说明:
查询结果会包含所有dept表中的部门。对于名为OPERATIONS的部门,由于没有员工属于该部门,因此对应的e.enamee.sal列将显示为NULL

示例3:在聚合查询中使用

(+)操作符同样可以用于聚合查询,例如统计每个部门的员工数量,包括那些没有员工的部门。

-- 统计各部门的员工数量,包括空部门
SELECT d.dname, COUNT(e.empno) AS emp_count
FROM dept d, emp e
WHERE d.deptno = e.deptno(+)
GROUP BY d.dname, d.deptno
ORDER BY d.deptno;

返回结果:

   dname    | emp_count 
------------+-----------
 ACCOUNTING |         2
 RESEARCH   |         1
 SALES      |         1
 OPERATIONS |         0
(4 rows)

结果说明:
对于OPERATIONS部门,由于没有匹配的员工,COUNT(e.empno)的结果将为0

兼容性说明

Oracle 19c及更高版本中,支持在WHERE子句中对多个表同时使用(+)操作符进行外连接。例如:

SELECT e.ename, d.dname, e.sal, s.grade
FROM emp e, dept d, salgrade s
WHERE e.deptno(+) = d.deptno AND e.sal(+) = s.hisal;

PolarDB PostgreSQL版(兼容Oracle)中,不同内核版本的处理方式不同:

  • 修订版本小于2.0.14.17.36.0:不支持。

  • 修订版本大于等于2.0.14.17.36.0:您可以通过设置参数polar_enable_transform_new_style_join_expr来启用此功能。

    说明

    此参数可在会话级、用户级或数据库级进行设置。同时,亦支持在控制台的参数模板中进行全局配置。

常见问题

为什么查询会报ORA-01468ORA-01719错误?

这通常是由于不正确地使用了(+)操作符。请检查以下常见错误:

  • 双侧使用(+):连接条件的等号两侧不能同时出现(+)

    -- 错误示例
    SELECT * FROM emp e, dept d WHERE e.deptno(+) = d.deptno(+);
  • OR条件中使用(+):包含(+)的条件不能与其他条件通过OR连接。

    -- 错误示例
    SELECT * FROM emp e, dept d WHERE (e.deptno = d.deptno(+) OR e.sal > 2000);