必须改,漏一个(+)会使LEFT JOIN变为INNER JOIN且不报错;因(+)是位置敏感语义标记,所有Nullable-Side条件均需带(+),否则优化器触发外连接消除,导致结果集静默丢失。

Oracle 12c 中的 (+) 语法必须改,不是“可以改”,而是“不改就埋雷”——尤其在迁移、升级或多人协作场景下,漏一个 (+) 就会让 LEFT JOIN 变成 INNER JOIN,且不报错、不预警,只悄悄少数据。
为什么 (+) 语法容易出错
Oracle 的 (+) 是位置敏感的语义标记,不是可选修饰符。它只作用于紧邻的列或表达式,且所有涉及 Nullable-Side(外连接中允许为空的那一侧)的条件都必须带 (+),否则会被优化器当作连接后过滤条件处理。
- 常见错误现象:
SELECT * FROM t1, t2 WHERE t1.id = t2.id(+) AND t2.name = 'cc'看似想查 t1 全量 + t2 中 name='cc' 的匹配行,实际只返回 t1 和 t2 都有匹配的行 - 根本原因:优化器发现
t2.name = 'cc'没带(+),于是把整个外连接重写为内连接(outer join elimination) - 性能影响:看似没变,但执行计划里
JOIN类型已变,统计信息、绑定变量窥探、物化视图重写等都可能随之失效
LEFT JOIN 场景:t2 是 Nullable-Side,所有 t2 条件都要进 ON
把 WHERE t1.id = t2.id(+) AND t2.name(+) = 'cc' 改成 ANSI 时,关键是识别哪些条件属于“连接逻辑”,哪些属于“结果过滤”。t2.name = 'cc' 是业务约束,应保留在连接条件中,而非移到 WHERE。
- 旧写法:
SELECT * FROM t1, t2 WHERE t1.id = t2.id(+) AND t2.name(+) = 'cc' - 正确 ANSI 写法:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id AND t2.name = 'cc' - 错误改法:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name = 'cc'—— 这又退回到原问题,NULL 行被 WHERE 过滤掉 - 注意:如果需要保留 t1 全量、同时对 t2 做额外过滤(比如只看某类状态),必须把该条件写进
ON,不能放WHERE
多表混合连接时,(+ ) 位置混乱极易连锁出错
三个及以上表用传统语法时,(+) 往往分散在不同 AND 子句中,人眼难判断哪边是 Nullable-Side;而 ANSI 语法强制显式声明每个 JOIN 的类型和条件边界。
- 旧写法示例:
SELECT * FROM t1, t2, t3 WHERE t1.id = t2.id(+) AND t2.pid = t3.id(+) AND t3.type(+) = 'X' - 对应 ANSI 写法:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id LEFT JOIN t3 ON t2.pid = t3.id AND t3.type = 'X' - 关键点:第二个
LEFT JOIN的ON必须包含t3.type = 'X',否则 t3 的过滤会作用于连接后结果,导致 t2 无匹配时整行丢失 - 兼容性提醒:Oracle 12c 完全支持 ANSI JOIN,但某些旧 JDBC 驱动(如 ojdbc6)对复杂 ANSI 语法解析有 Bug,建议搭配 ojdbc8 使用
改完别忘了验证:结果集比对 + 执行计划确认
语法改完只是第一步,真正风险藏在语义一致性里。很多团队只校验“能跑”,却跳过“是否等价”。
- 最简验证法:对同一数据集,分别运行新旧 SQL,用
MINUS检查差集:OLD_SQL MINUS NEW_SQL和NEW_SQL MINUS OLD_SQL都应为空 - 执行计划必查项:确认新 SQL 的
JOIN类型确实是NESTED LOOPS OUTER或HASH JOIN OUTER,而不是NESTED LOOPS(无 OUTER) - 容易被忽略的点:如果旧 SQL 里用了
ROWNUM或子查询,ANSI 改写后可能触发不同的谓词推入(predicate pushdown)行为,需单独测试分页逻辑


















