SQL 从 Oracle 转换为 ANSI JOIN

SQL convert from Oracle to ANSI JOIN

如何从旧的 Oracle 连接类型转换为 ANSI 连接类型,为什么?

SELECT *
FROM a, b, c
WHERE b.id (+) = a.id1
AND b.xxx = c.yyy
AND c.id (+) = a.id2

--应该是这个1吧?

select * from
A
left outer join B on B.ID = A.ID1
left outer join C on C.ID = A.ID2 AND B.xxx = C.yyy

--还是这2个?

select * from
A
left outer join C on C.ID = A.ID2
left outer join B on B.ID = A.ID1 AND B.xxx = C.yyy

根据 Oracle documentation:

If the WHERE clause contains a condition that compares a column from table B with a constant, then the (+) operator must be applied to the column so that Oracle returns the rows from table A for which it has generated nulls for this column. Otherwise Oracle returns only the results of a simple join.

因此 bc 之间存在内部连接。并且由于整体条件,这会将所有连接变成 INNER JOINbc 中需要有效值才能使该条件起作用。

我认为等价的逻辑是:

SELECT *
FROM a JOIN
     b
     ON b.id = a.id1 JOIN
     c
     ON c.id = a.id2 AND b.xxx = c.yyyy;

也就是说,简单的相等消除将外连接变成内连接。

当然可以测试一下

如果您 运行 简单 explain plan 并检查结果,您可能会看到它生成两个 inner joins:

explain plan set statement_id = 'test' for
SELECT *
FROM a, b, c
WHERE
b.id (+) = a.id1
AND b.xxx = c.yyy
AND c.id (+) = a.id2
select *
from table(dbms_xplan.display(null, 'test'))
| PLAN_TABLE_OUTPUT                                                            |
| :--------------------------------------------------------------------------- |
| Plan hash value: 1502482080                                                  |
|                                                                              |
| ---------------------------------------------------------------------------- |
| | Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     | |
| ---------------------------------------------------------------------------- |
| |   0 | SELECT STATEMENT    |      |     1 |    78 |     6   (0)| 00:00:01 | |
| |*  1 |  HASH JOIN          |      |     1 |    78 |     6   (0)| 00:00:01 | |
| |*  2 |   HASH JOIN         |      |     1 |    52 |     4   (0)| 00:00:01 | |
| |   3 |    TABLE ACCESS FULL| A    |     1 |    26 |     2   (0)| 00:00:01 | |
| |   4 |    TABLE ACCESS FULL| B    |     1 |    26 |     2   (0)| 00:00:01 | |
| |   5 |   TABLE ACCESS FULL | C    |     1 |    26 |     2   (0)| 00:00:01 | |
| ---------------------------------------------------------------------------- |
|                                                                              |
| Predicate Information (identified by operation id):                          |
| ---------------------------------------------------                          |
|                                                                              |
|    1 - access("B"."XXX"="C"."YYY" AND "C"."ID"="A"."ID2")                    |
|    2 - access("B"."ID"="A"."ID1")                                            |
|                                                                              |
| Note                                                                         |
| -----                                                                        |
|    - dynamic statistics used: dynamic sampling (level=2)                     |
explain plan set statement_id = 'test1' for
select *
from a
  left join b
    on b.id = a.id1
  left join c
    on c.id = a.id2
    and b.xxx = c.yyy
select *
from table(dbms_xplan.display(null, 'test1'))
| PLAN_TABLE_OUTPUT                                                            |
| :--------------------------------------------------------------------------- |
| Plan hash value: 2316364204                                                  |
|                                                                              |
| ---------------------------------------------------------------------------- |
| | Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     | |
| ---------------------------------------------------------------------------- |
| |   0 | SELECT STATEMENT    |      |     1 |    78 |     6   (0)| 00:00:01 | |
| |*  1 |  HASH JOIN OUTER    |      |     1 |    78 |     6   (0)| 00:00:01 | |
| |*  2 |   HASH JOIN OUTER   |      |     1 |    52 |     4   (0)| 00:00:01 | |
| |   3 |    TABLE ACCESS FULL| A    |     1 |    26 |     2   (0)| 00:00:01 | |
| |   4 |    TABLE ACCESS FULL| B    |     1 |    26 |     2   (0)| 00:00:01 | |
| |   5 |   TABLE ACCESS FULL | C    |     1 |    26 |     2   (0)| 00:00:01 | |
| ---------------------------------------------------------------------------- |
|                                                                              |
| Predicate Information (identified by operation id):                          |
| ---------------------------------------------------                          |
|                                                                              |
|    1 - access("C"."ID"(+)="A"."ID2" AND "B"."XXX"="C"."YYY"(+))              |
|    2 - access("B"."ID"(+)="A"."ID1")                                         |
|                                                                              |
| Note                                                                         |
| -----                                                                        |
|    - dynamic statistics used: dynamic sampling (level=2)                     |

db<>fiddle here

你的选择都没有:

select *
from   A
       left outer join B on B.ID = A.ID1
       left outer join C on C.ID = A.ID2 AND B.xxx = C.yyy

可以写成:

SELECT *
FROM a, b, c
WHERE b.id (+) = a.id1
AND   b.xxx = c.yyy (+)
AND   c.id (+) = a.id2

和:

select *
from   A
       left outer join C on C.ID = A.ID2
       left outer join B on B.ID = A.ID1 AND B.xxx = C.yyy

可以写成:

SELECT *
FROM a, b, c
WHERE b.id (+) = a.id1
AND   b.xxx (+) = c.yyy
AND   c.id (+) = a.id2

你拥有的是:

SELECT *
FROM   a
       INNER JOIN b ON (a.id1 = b.id)
       INNER JOIN c ON (a.id2 = c.id AND b.xxx = c.yyy)

why?

SELECT *
FROM   a, b, c
WHERE  b.id (+) = a.id1
AND    b.xxx = c.yyy
AND    c.id (+) = a.id2

行:

AND    b.xxx = c.yyy

要求有b行和c行;当存在左外连接时不会发生这种情况,因此连接等同于内连接并且查询可以重写为:

SELECT *
FROM   a, b, c
WHERE  b.id = a.id1
AND    b.xxx = c.yyy
AND    c.id = a.id2

然后所有连接都是内部连接就更清楚了。


您可能打算写的是:

select *
from   A,
       (
         SELECT b.id AS b_id,
                c.id AS c_id,
                b.xxx,
                c.yyy
         FROM   b, c
         WHERE  b.xxx = c.yyy
       ) bc
WHERE  bc.b_id (+) = a.id1
AND    bc.c_id (+) = a.id2

这将是:

select *
from   A
       left outer join (
         SELECT b.id AS b_id,
                c.id AS c_id,
                b.xxx,
                c.yyy
         FROM   b
                INNER JOIN c ON b.xxx = c.yyy
       ) bc
       on bc.b_id = a.id1 AND bc.c_id = a.id2

db<>fiddle here

您也可以使用 Oracle SQL Developer 作弊

粘贴您的 ANSI SQL 查询或 Oracle SQL 查询,select 它,右键单击,然后使用转换功能。

我们的 SQL 解析器将为您重写 JOINS。

所以这适用于任何查询,而不仅仅是您在 question/scenario 中的查询。

您当然应该检查查询返回的执行计划和数据,以确保它们在功能上是等效的。