#### 13.2.11.7 相关子查询

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

一种*相关子查询*是一个子查询，其中包含对也出现在外部查询中的表的引用。例如：

```
SELECT * FROM t1
  WHERE column1 = ANY (SELECT column1 FROM t2
                       WHERE t2.column2 = t1.column2);
```

请注意，子查询包含对`t1`，即使子查询的`从`子句未提及表`t1`.因此，MySQL 在子查询之外查找，并发现`t1`在外部查询中。

假设那张桌子`t1`包含一行，其中`列 1 = 5`和`列 2 = 6`;同时，表`t2`包含一行，其中`列 1 = 5`和`列 2 = 7`.简单的表达`... WHERE column1 = ANY (SELECT column1 FROM t2)`将是`真的`，但在本例中，`在哪里`子查询中的子句是`错误的`（因为`(5,6)`不等于`(5,7)`)，所以整个表达式是`错误的`.

**范围规则：**MySQL 从内到外进行评估。例如：

```
SELECT column1 FROM t1 AS x
  WHERE x.column1 = (SELECT column1 FROM t2 AS x
    WHERE x.column1 = (SELECT column1 FROM t3
      WHERE x.column2 = t3.column1));
```

在这份声明中，`x.column2`必须是表中的一列`t2`因为`SELECT column1 FROM t2 AS x ...`重命名`t2`.它不是表中的列`t1`因为`从 t1 中选择 column1 ...`是一个外部查询，即*更远的地方*.

从 MySQL 8.0.24 开始，优化器可以将相关的标量子查询转换为派生表，当[`subquery_to_derived`](switchable-optimizations.html#optflag_subquery-to-derived)国旗[`优化器开关`](server-system-variables.html#sysvar_optimizer_switch)变量已启用。考虑此处显示的查询：

```
SELECT * FROM t1
    WHERE ( SELECT a FROM t2
              WHERE t2.a=t1.a ) > 0;
```

为了避免对给定的派生表多次实现，我们可以改为实现一次派生表，该表在内部查询中引用的表的连接列上添加分组（`t2.a`) 然后在提升的谓词上进行外连接 (`t1.a = 派生的.a`) 以便选择正确的组以与外行匹配。（如果子查询已经有一个明确的分组，额外的分组被添加到分组列表的末尾。）之前显示的查询可以这样重写：

```
SELECT t1.* FROM t1
    LEFT OUTER JOIN
        (SELECT a, COUNT(*) AS ct FROM t2 GROUP BY a) AS derived
    ON  t1.a = derived.a
        AND
        REJECT_IF(
            (ct > 1),
            "ERROR 1242 (21000): Subquery returns more than 1 row"
            )
    WHERE derived.a > 0;
```

在重写的查询中，`拒绝_IF()`表示测试给定条件的内部函数（这里，比较`ct > 1`) 并引发给定错误（在这种情况下，[`ER_SUBQUERY_NO_1_ROW`](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_subquery_no_1_row)) 如果条件为真。这反映了优化器在评估`加入`要么`在哪里`子句，在评估任何提升的谓词之前，仅当子查询不返回多于一行时才会这样做。

如果满足以下条件，则可以执行此类转换：

-   子查询可以是[`选择`](select.html)列表，`在哪里`条件，或`拥有`条件，但不能成为[`加入`](join.html)条件，并且不能包含`限制`要么`抵消`条款。此外，子查询不能包含任何集合操作，例如[`联盟`](union.html).

-   这`在哪里`子句可以包含一个或多个谓词，与`和`.如果`在哪里`子句包含一个`要么`子句，它不能被转换。至少其中一项`在哪里`子句谓词必须符合转换条件，并且不能拒绝转换。

-   为了有资格进行转型，一个`在哪里`子句谓词必须是一个相等谓词，其中每个操作数都应该是一个简单的列引用。没有其他谓词（包括其他比较谓词）有资格进行转换。谓词必须使用相等运算符[`=`](comparison-operators.html#operator_equal)进行比较；零安全的[`≪=>`](comparison-operators.html#operator_equal-to)在此上下文中不支持运算符。

-   一种`在哪里`仅包含内部引用的子句谓词不符合转换条件，因为它可以在分组之前进行评估。一种`在哪里`仅包含外部引用的子句谓词有资格进行转换，即使它可以提升到外部查询块。这可以通过添加基数检查而不在派生表中进行分组来实现。

-   要获得资格，一个`在哪里`子句谓词必须有一个仅包含内部引用的操作数和一个仅包含外部引用的操作数。如果谓词由于此规则不符合条件，则拒绝查询转换。

-   相关列只能出现在子查询的`在哪里`子句（而不是在`选择`清单，一个`加入`要么`订购方式`条款，一个`通过...分组`列表，或`拥有`条款）。子查询的派生表中也不能有任何相关列`从`列表。

-   相关列不能包含在聚合函数的参数列表中。

-   必须在直接包含考虑进行转换的子查询的查询块中解析相关列。

-   关联列不能出现在嵌套标量子查询中`在哪里`条款。

-   子查询不能包含任何窗口函数，并且不能包含在子查询外部的查询块中聚合的任何聚合函数。一种[`数数（）`](aggregate-functions.html#function_count)聚合函数，如果包含在`选择`子查询的列表元素，必须在最顶层，并且不能是表达式的一部分。

    也可以看看[第 13.2.11.8 节，“派生表”](derived-tables.html).
