本文档介绍如何在 TDSQL Boundless 数据库中使用子查询。子查询是嵌套在另一个查询内部的 SQL 查询,允许你在一条语句中使用另一个查询的结果。
子查询的分类
在 TDSQL Boundless 中,子查询通常有以下几种形式:
标量子查询
标量子查询返回单行单列的值,可以出现在 SELECT 列表、WHERE 条件等任何需要单个值的位置。其关键特征是子查询的结果等价于一个常量值。
SELECTc_name,c_acctbal,(SELECT AVG(c_acctbal) FROM customer) AS avg_balanceFROM customerLIMIT 5;
派生表
派生表是放在
FROM 子句中的子查询,作为一个临时表参与后续查询。其关键特征是子查询必须用括号包裹并指定别名。SELECT seg.c_mktsegment, seg.cntFROM (SELECT c_mktsegment, COUNT(*) AS cntFROM customerGROUP BY c_mktsegment) segORDER BY seg.cnt DESC;
存在性测试
通过
EXISTS、NOT EXISTS、IN、NOT IN 等关键字判断子查询是否返回数据,结果是布尔值。其关键特征是不关心子查询返回的具体值,只关心是否有行存在。-- EXISTS:判断是否存在匹配行SELECT c_name FROM customer cWHERE EXISTS (SELECT 1 FROM orders o WHERE o.o_custkey = c.c_custkey);-- IN:判断值是否在结果集中SELECT c_name FROM customerWHERE c_nationkey IN (SELECT n_nationkey FROM nation WHERE n_name = 'CHINA');
集合比较
使用
ANY、ALL、SOME 关键字将一个值与子查询返回的结果集进行比较。其关键特征是比较运算符(=、>、< 等)与 ANY/ALL 组合使用。-- = ANY 等价于 INSELECT c_name, c_acctbal FROM customerWHERE c_acctbal > ANY (SELECT o_totalprice FROM orders WHERE o_orderstatus = 'F');-- > ALL:大于子查询返回的所有值SELECT c_name, c_acctbal FROM customerWHERE c_acctbal > ALL (SELECT AVG(c_acctbal) FROM customer GROUP BY c_mktsegment);
作为比较运算符操作数
子查询直接作为比较运算符(
>、<、=、>=、<=、<>)的一侧操作数。其关键特征是子查询必须返回单行单列(即标量),与标量子查询的区别在于它出现在 WHERE/HAVING 的比较条件中。SELECT o_orderkey, o_totalpriceFROM ordersWHERE o_totalprice > (SELECT AVG(o_totalprice) FROM orders);
子查询的相关性
根据子查询是否引用了外层查询的列,可分为非关联子查询和关联子查询两类。
非关联子查询
非关联子查询不引用外层查询的任何列,其结果独立于外层查询。TDSQL Boundless 会先执行内层子查询,将结果作为常量代入外层查询。
查询账户余额高于所有客户平均余额的客户:
SELECT c_custkey, c_name, c_acctbalFROM customerWHERE c_acctbal > (SELECT AVG(c_acctbal) FROM customer);
TDSQL Boundless 在处理该查询时,会先执行内层子查询:
SELECT AVG(c_acctbal) FROM customer;
假设计算结果为
4990.51,则外层查询等价于:SELECT c_custkey, c_name, c_acctbalFROM customerWHERE c_acctbal > 4990.51;
使用 IN 子查询查找有过订单的客户:
SELECT c_custkey, c_name, c_mktsegmentFROM customerWHERE c_custkey IN (SELECT DISTINCT o_custkey FROM ordersWHERE o_orderdate >= '1995-01-01');
内层子查询独立执行,返回一组
o_custkey 值,外层查询在这组值中进行匹配。关联子查询
关联子查询引用了外层查询的列,因此内层查询的结果依赖于外层查询当前正在处理的行。从逻辑上看,关联子查询需要对外层的每一行都重新执行一次内层查询。
查询每个客户中金额最大的订单:
SELECT o_orderkey, o_custkey, o_totalprice, o_orderdateFROM orders o1WHERE o_totalprice = (SELECT MAX(o2.o_totalprice)FROM orders o2WHERE o2.o_custkey = o1.o_custkey);
内层子查询引用了外层的
o1.o_custkey,对于外层每一行,子查询计算该客户的最大订单金额,然后只保留金额等于最大值的行。查询账户余额高于同市场细分客户平均余额的客户:
SELECT c1.c_custkey, c1.c_name, c1.c_mktsegment, c1.c_acctbalFROM customer c1WHERE c1.c_acctbal > (SELECT AVG(c2.c_acctbal)FROM customer c2WHERE c2.c_mktsegment = c1.c_mktsegment);
TDSQL Boundless 优化器会尝试对关联子查询进行去关联(Unnesting)优化,将其改写为等价的 JOIN 查询以提升性能。例如上述查询可能被改写为:
SELECT c1.c_custkey, c1.c_name, c1.c_mktsegment, c1.c_acctbalFROM customer c1INNER JOIN (SELECT c_mktsegment, AVG(c_acctbal) AS avg_acctbalFROM customerGROUP BY c_mktsegment) c2 ON c1.c_mktsegment = c2.c_mktsegmentWHERE c1.c_acctbal > c2.avg_acctbal;
改写后的查询只需对
customer 表扫描两次(一次聚合、一次连接),而不是对每个客户都执行一次子查询,性能显著提升。常见子查询场景
EXISTS 子查询
EXISTS 用于判断子查询是否返回了至少一行数据,常用于存在性检查。查询有过高金额订单(金额 > 300000)的客户:
SELECT c.c_custkey, c.c_name, c.c_acctbalFROM customer cWHERE EXISTS (SELECT 1 FROM orders oWHERE o.o_custkey = c.c_custkeyAND o.o_totalprice > 300000);
查询没有下过订单的客户(NOT EXISTS):
SELECT c.c_custkey, c.c_name, c.c_phoneFROM customer cWHERE NOT EXISTS (SELECT 1 FROM orders oWHERE o.o_custkey = c.c_custkey);
NOT EXISTS 在语义上等价于 LEFT JOIN ... WHERE ... IS NULL,但在某些场景下两者的执行效率不同,可通过 EXPLAIN 进行比较。IN 子查询
IN 用于判断某个值是否在子查询返回的结果集中。查询来自 ASIA 区域国家的客户:
SELECT c_custkey, c_name, c_nationkeyFROM customerWHERE c_nationkey IN (SELECT n_nationkey FROM nationWHERE n_regionkey IN (SELECT r_regionkey FROM regionWHERE r_name = 'ASIA'));
IN 与 EXISTS 的选择:当子查询返回的结果集较小时,
IN 和 EXISTS 性能差异不大;当外层表较小而子查询结果集较大时,EXISTS 通常更高效。标量子查询
标量子查询返回单个值,可以出现在
SELECT 列表、WHERE 条件等位置。在 SELECT 列表中使用标量子查询 — 查询每个订单及其客户名称:
SELECTo_orderkey,o_totalprice,o_orderdate,(SELECT c_name FROM customer WHERE c_custkey = o_custkey) AS customer_nameFROM ordersWHERE o_orderdate = '1995-03-15';
SELECT 列表中的标量子查询在逻辑上会对每一行执行一次,当外层行数较多时建议改写为 JOIN:
SELECTo.o_orderkey,o.o_totalprice,o.o_orderdate,c.c_name AS customer_nameFROM orders oINNER JOIN customer c ON o.o_custkey = c.c_custkeyWHERE o.o_orderdate = '1995-03-15';
派生表(FROM 子句中的子查询)
子查询放在
FROM 子句中作为临时表(派生表)使用。查询每个市场细分中账户余额最高的客户:
SELECT c.c_custkey, c.c_name, c.c_mktsegment, c.c_acctbalFROM customer cINNER JOIN (SELECT c_mktsegment, MAX(c_acctbal) AS max_acctbalFROM customerGROUP BY c_mktsegment) seg_max ON c.c_mktsegment = seg_max.c_mktsegmentAND c.c_acctbal = seg_max.max_acctbal;
统计各订单优先级的订单数量和平均金额,只保留数量超过 10000 的优先级:
SELECT *FROM (SELECTo_orderpriority,COUNT(*) AS order_count,AVG(o_totalprice) AS avg_amountFROM ordersGROUP BY o_orderpriority) summaryWHERE order_count > 10000ORDER BY order_count DESC;