#### 8.2.1.10 外部连接简化

表中的表达式`从`在许多情况下，查询的子句被简化了。

在解析器阶段，具有右外连接操作的查询被转换为仅包含左连接操作的等效查询。在一般情况下，执行转换使得该右连接：

```
(T1, ...) RIGHT JOIN (T2, ...) ON P(T1, ..., T2, ...)
```

变成这个等价的左连接：

```
(T2, ...) LEFT JOIN (T1, ...) ON P(T1, ..., T2, ...)
```

形式的所有内连接表达式`T1 INNER JOIN T2 ON P(T1,T2)`被列表替换`T1,T2`,`P(T1,T2)`被连接为`在哪里`条件（或嵌入连接的连接条件，如果有的话）。

当优化器评估外部连接操作的计划时，它只考虑对于每个这样的操作，外部表在内部表之前被访问的计划。优化器的选择是有限的，因为只有这样的计划才能使用嵌套循环算法执行外连接。

考虑这种形式的查询，其中`R(T2)`大大缩小了表中匹配行的数量`T2`：

```
SELECT * T1 FROM T1
  LEFT JOIN T2 ON P1(T1,T2)
  WHERE P(T1,T2) AND R(T2)
```

如果查询按写入执行，优化器别无选择，只能访问限制较少的表`T1`在更受限制的表之前`T2`，这可能会产生一个非常低效的执行计划。

相反，MySQL 将查询转换为没有外连接操作的查询，如果`在哪里`条件为空拒绝。（也就是说，它将外连接转换为内连接。）如果条件的计算结果为`错误的`要么`未知`对于任何`空值`-为操作生成的补充行。

[](<>)[](<>)

因此，对于这个外部连接：

```
T1 LEFT JOIN T2 ON T1.A=T2.A
```

诸如此类的条件被拒绝为空，因为它们不能为任何`空值`-补行（与`T2`列设置为`空值`):

```
T2.B IS NOT NULL
T2.B > 3
T2.C <= T1.C
T2.B < 2 OR T2.C > 1
```

诸如此类的条件不会被 null 拒绝，因为它们可能对`空值`- 补充行：

```
T2.B IS NULL
T1.B < 3 OR T2.B IS NOT NULL
T1.B < 3 OR T2.B > 3
```

检查外连接操作的条件是否为空拒绝的一般规则很简单：

-   它的形式`A 不为空`， 在哪里`一种`是任何内部表的属性

-   它是一个谓词，包含对内部表的引用，其计算结果为`未知`当它的一个论点是`空值`

-   它是一个包含一个 null 拒绝条件作为连词的连词

-   它是 null 拒绝条件的析取

    对于查询中的一个外连接操作，条件可以为空拒绝，而对另一个条件不能为空拒绝。在这个查询中，`在哪里`对于第二个外部连接操作，条件为 null 拒绝，但对于第一个外部连接操作不为 null 拒绝：


```
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 LEFT JOIN T3 ON T3.B=T1.B
  WHERE T3.C > 0
```

如果`在哪里`对于查询中的外连接操作，条件为空拒绝，则外连接操作被内连接操作替换。

例如，在前面的查询中，第二个外连接是 null 拒绝的，可以用内连接代替：

```
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 INNER JOIN T3 ON T3.B=T1.B
  WHERE T3.C > 0
```

对于原始查询，优化器只评估与单表访问顺序兼容的计划`T1,T2,T3`.对于重写的查询，它还考虑访问顺序`T3,T1,T2`.

一个外连接操作的转换可能会触发另一个外连接操作的转换。因此，查询：

```
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 LEFT JOIN T3 ON T3.B=T2.B
  WHERE T3.C > 0
```

首先转换为查询：

```
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 INNER JOIN T3 ON T3.B=T2.B
  WHERE T3.C > 0
```

这相当于查询：

```
SELECT * FROM (T1 LEFT JOIN T2 ON T2.A=T1.A), T3
  WHERE T3.C > 0 AND T3.B=T2.B
```

剩余的外连接操作也可以用内连接代替，因为条件`T3.B=T2.B`被 null 拒绝。这会导致查询完全没有外部连接：

```
SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3
  WHERE T3.C > 0 AND T3.B=T2.B
```

有时优化器成功替换了嵌入外连接操作，但无法转换嵌入外连接。以下查询：

```
SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A
  WHERE T3.C > 0
```

转换为：

```
SELECT * FROM T1 LEFT JOIN
              (T2 INNER JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A
  WHERE T3.C > 0
```

只能将其重写为仍包含嵌入外连接操作的表单：

```
SELECT * FROM T1 LEFT JOIN
              (T2,T3)
              ON (T2.A=T1.A AND T3.B=T2.B)
  WHERE T3.C > 0
```

任何在查询中转换嵌入外连接操作的尝试都必须考虑嵌入外连接的连接条件以及`在哪里`健康）状况。在这个查询中，`在哪里`内嵌外连接的条件不是空拒绝，而是内嵌外连接的连接条件`T2.A=T1.A 和 T3.C=T1.C`被 null 拒绝：

```
SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A AND T3.C=T1.C
  WHERE T3.D > 0 OR T1.D > 0
```

因此，查询可以转换为：

```
SELECT * FROM T1 LEFT JOIN
              (T2, T3)
              ON T2.A=T1.A AND T3.C=T1.C AND T3.B=T2.B
  WHERE T3.D > 0 OR T1.D > 0
```
