子查询
子查询是指嵌套在一个上层查询中的查询。上层的查询一般被称为父查询或外层查询。子查询的结果作为输入传递回"父查询"或"外部查询"。父查询将这个值结合到计算中,以便确定最后的输出。
SQL 语言允许多层嵌套查询,即一个子查询中还可以嵌套其他子查询。同时,子查询可以出现在 SQL 语句中的各种子句中,例如 SELECT 语句,FROM 语句,WHERE 语句等。
子查询
在数据库中,子查询可以分成有依赖关系的子查询和没有依赖关系的子查询。有依赖关系的子查询是指该子查询的执行依赖了外部查询的变量,所以这种子查询通常会被计算多次。没有依赖关系的子查询是指该子查询的执行不依赖外部查询的变量,这种子查询一般只需要计算一次。下面的示例分别展示了没有依赖关系的子查询和有依赖关系的子查询。
-
没有依赖关系的子查询:
SELECT * FROM t1 WHERE t1.a IN (SELECT t2.a FROM t2); -
有依赖关系的子查询,子查询中用到了外层查询变量
t1.b:SELECT * FROM t1 WHERE t1.a IN (SELECT t2.a FROM T2 WHERE t2.b = t1.b);
标量子查询
结果为单个元素的子查询称为标量子查询,查询结果可以作为标量操作数,并具有标量操作数的基本特性,例如数据类型、长度、是否可以为 NULL 等。例如以下查询返回的结果,就可以作为标量操作数:
CREATE TABLE t1 (c1 INTEGER, c2 VARCHAR(20) NOT NULL);
INSERT INTO t1 VALUES (1, 'OceanBase');
SELECT (SELECT c2 FROM t1);
+---------------------+
| (SELECT c2 FROM t1) |
+---------------------+
| OceanBase |
+---------------------+
1 row in set (0.001 sec)
以上查询返回 VARCHAR 类型,长度为 20 的值 OceanBase,字符集与字符序服从默认值。标量子查询所选值的可空性不会被复制,虽然 c2 列具有 NOT NULL 约束,但如果子查询结果为空,则结果仍会返回 NULL。
虽然返回单个元素,但标量子查询不能用于所有语句,如果语句仅允许字面量,则不能使用标量子查询来替代。
下面的例子可以帮助您更好地了解标量子查询。
该查询返回 2,是由于表 t2 中包含了与 t1 中同名的列 c1。
CREATE TABLE t1 (c1 INT);
INSERT INTO t1 VALUES (1);
CREATE TABLE t2 (c1 INT);
INSERT INTO t2 VALUES (2);
SELECT (SELECT c1 FROM t2) FROM t1;
+---------------------+
| (SELECT s1 FROM t2) |
+---------------------+
| 2 |
+---------------------+
1 row in set (0.001 sec)
此外,标量子查询可以是表达式的一部分,例如:
CREATE TABLE t1 (c1 INTEGER, c2 VARCHAR(20) NOT NULL);
INSERT INTO t1 VALUES (1, 'OceanBase');
SELECT HEX((SELECT c2 FROM t1 WHERE c1=1)) FROM t1;
+-------------------------------------+
| HEX((SELECT c2 FROM t1 WHERE c1=1)) |
+-------------------------------------+
| 4F6365616E42617365 |
+-------------------------------------+
1 row in set (0.001 sec)
子查询的比较
子查询常见的语法如下所示:
operand operator {ANY | SOME | ALL} (subquery)
operand LIKE (subquery)
operand {IN | NOT IN | EXISTS | NOT EXISTS} (subquery)
其中,operand 是用于与子查询结果比较的操作数,operator 可以是以下操作符:
-
等于(
=或<=>) -
大于(
>) -
小于(
<) -
大于等于(
>=) -
小于等于(
<=) -
不等于(
!=或<>) -
LIKE用于字符或字符串比较。
例如以下子查询用于查询 t1 中 c1 列与 t2 中最大的 c2 列相同的值,该查询不可被改写为 t1 与 t2 的联接。
CREATE TABLE t1 (c1 INT);
INSERT INTO t1 VALUES (1);
CREATE TABLE t2 (c2 INT);
INSERT INTO t2 VALUES (2);
SELECT * FROM t1 WHERE c1 = (SELECT MAX(c2) FROM t2);
与标 量的比较,子查询必须返回标量。与行构造函数进行比较,子查询必须是一个行子查询,并返回具有与行构造函数相同属性的行。
显然,只有标量子查询的结果可用于直接比较,如果子查询返回一列值,则需要对集合进行处理。seekdb 提供了以下用于将集合化为标量值的方式:
-
聚合函数:将集合中的所有值聚合为一个值,将该值与操作数进行比较。
-
ANY | SOME:将集合中所有值与操作数比较,如果存在满足比较条件的行,则返回TRUE。SOME是ANY的别名。 -
ALL:将集合中的所有值与操作数比较,如果所有行都满足比较条件,则返回TRUE。 -
IN:如果操作数属于集合,则返回TRUE。IN等价于= ANY。 -
NOT IN:如果操作数不属于集合,则返回TRUE。NOT IN等价于<> ALL。 -
EXIST:如果子查询有返回行,则返回TRUE。 -
NOT EXIST:如果子查询无返回行,则返回TRUE。
行子查询
标量子查询或列子查询返回标量或一列值,而行子查询则返回的是单个行,因此可以返回多个列值。
例如以下子查询仅返回单行:
CREATE TABLE t1(id INTEGER PRIMARY KEY,c1 INTEGER,c2 INTEGER);
CREATE TABLE t2(id INTEGER PRIMARY KEY,c3 INTEGER,c4 INTEGER);
SELECT * FROM t1
WHERE ROW(c1,c2) = (SELECT c3, c4 FROM t2 WHERE id = 10);
如果 c3,c4 的值等于 t1 中的任意 c1,c2 值对,则返回所有满足条件的 c1,c2。如果不存在,则返回空集。此外,如果子查询中没有指定 WHERE 条件,则子查询可能会返回多行,此时查询会发生错误。行子查询只能返回单行。
表达式 ROW(c1,c2) 可以简写为 (c1,c2),ROW() 称为行构造函数。行构造函数和子查询返回的行必须包含相同数量的值。行构造函数用于与返回两列或更多列的子查询进行比较,行构造函数不能与返回单列的列子查询同时使用,例如以下查询会返回语法错误:
SELECT * FROM t1 WHERE ROW(1) = (SELECT column1 FROM t2);
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your OceanBase version for the right syntax to use near ') = (SELECT c1 FROM t2)' at line 1
行构造函数在优化器中会被展开,例如以下两 条 SQL 是等价的:
SELECT * FROM t1 WHERE (c1,c2) = (1,1);
SELECT * FROM t1 WHERE c1 = 1 AND c2 = 1;