一个 相关子查询 是一个包含对外部查询中表的引用的子查询。例如:
SELECT * FROM t1
WHERE column1 = ANY (SELECT column1 FROM t2
WHERE t2.column2 = t1.column2);
注意,子查询包含对 t1 的列的引用,即使子查询的 FROM 子句不提到表 t1。因此,MySQL 会在外部查询中查找 t1。
假设表 t1 包含一行,其中 column1 = 5 和 column2 = 6;同时,表 t2 包含一行,其中 column1 = 5 和 column2 = 7。简单的表达式 ... WHERE column1 = ANY (SELECT column1 FROM t2) 将是 TRUE,但是在这个示例中,子查询中的 WHERE 子句是 FALSE(因为 (5,6) 不等于 (5,7)),因此整个表达式是 FALSE。
作用域规则: 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 的列,因为 SELECT column1 FROM t1 ... 是一个外部查询,位于更外层。
优化器可以将相关标量子查询转换为派生表,当 subquery_to_derived 标志启用时。考虑以下查询:
SELECT * FROM t1
WHERE ( SELECT a FROM t2
WHERE t2.a=t1.a ) > 0;
为了避免对派生表的多次物化,我们可以将派生表物化一次,添加一个对连接列的分组(来自内部查询的表 t2.a),然后对外连接 lift predicate (t1.a = derived.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;
在重写的查询中,REJECT_IF() 代表一个内部函数,它测试给定的条件(这里是比较 ct > 1)并在条件为 true 时引发给定的错误(在这种情况下,为 ER_SUBQUERY_NO_1_ROW)。这反映了优化器在评估 JOIN 或 WHERE 子句之前执行的基数检查,以确定子查询是否返回多于一行。
这种类型的转换可以执行,前提是满足以下条件:
-
子查询可以是
SELECT列表、WHERE条件或HAVING条件的一部分,但不能是JOIN条件的一部分,也不能包含LIMIT或OFFSET子句。此外,子查询不能包含任何集合操作,如UNION。 -
WHERE子句可以包含一个或多个谓词,使用AND结合。如果WHERE子句包含OR子句,则不能转换。至少一个WHERE子句谓词必须符合转换条件,且不能拒绝转换。 -
要符合转换条件,
WHERE子句谓词必须是一个等式谓词,其中每个操作数都应该是一个简单的列引用。其他谓词——包括其他比较谓词——不符合转换条件。谓词必须使用等式运算符=进行比较;在此上下文中,不支持 null 安全的≪=>运算符。 -
仅包含内部引用的
WHERE子句谓词不符合转换条件,因为它可以在分组之前评估。仅包含外部引用的WHERE子句谓词符合转换条件,即使它可以被提升到外部查询块中。这是通过在派生表中添加基数检查而实现的,不需要分组。 -
要符合转换条件,
WHERE子句谓词必须有一个操作数仅包含内部引用,另一个操作数仅包含外部引用。如果谓词不符合这个规则,查询的转换将被拒绝。 -
相关列只能出现在子查询的
WHERE子句中(而不能出现在SELECT列表、JOIN或ORDER BY子句、GROUP BY列表或HAVING子句中)。此外,在子查询的FROM列表中的派生表中也不能包含相关列。 -
相关列不能包含在聚合函数的参数列表中。
-
相关列必须在直接包含子查询的查询块中解析。
-
相关列不能出现在
WHERE子句中的嵌套标量子查询中。 -
子查询不能包含任何窗口函数,并且不能包含任何聚合函数,该函数聚合在子查询外的查询块中。
COUNT()聚合函数,如果包含在子查询的SELECT列表元素中,必须位于顶层,不能是表达式的一部分。