标量子查询的取值规则
标量子查询是出现在「需要一个值」位置上的子查询,例如查询列表、比较运算的一侧、更新语句的赋值处。规则有两条:必须至多返回一行一列,返回多行直接报错;返回零行时结果不是报错而是 NULL,它会继续参与外层表达式并可能把整行结果变成 NULL。这两条规则决定了它的适用边界,也是线上最常见的事故点。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| 常见位置 | 查询列表、比较条件、赋值语句的右侧 |
| 行数限制 | 至多一行一列,多行时语句直接失败 |
| 空结果行为 | 没有匹配行时返回 NULL,而不是报错 |
| 典型报错 | MySQL 错误 1242;PostgreSQL 提示子查询返回多于一行 |
典型使用场景
- 给明细行附一个全局参考值,例如全部订单里的最大金额
- 取某个对象最近一次操作时间,用于计算间隔天数
- 更新语句里把子查询结果赋给目标列
- 配置类取值:取一条系统参数作为默认值参与计算
- 兼容性改写:把原先的相关子查询换成更易读的取值形式
示例与报错写法
示例与报错写法
-- 正确用法:子查询保证只返回一行一列
SELECT o.id, o.amount,
(SELECT MAX(amount) FROM orders) AS max_amount
FROM orders o
WHERE o.id <= 10;
-- 没有匹配行时返回 NULL,参与运算会让整行结果变空
SELECT u.id, (SELECT MAX(o.amount) FROM orders o WHERE o.user_id = u.id) AS max_amt
FROM users u;
-- 危险写法:子查询未限制返回行数,多行时直接报错
SELECT u.id, (SELECT o.amount FROM orders o WHERE o.user_id = u.id) AS amt FROM users u;
-- 稳妥写法:显式收敛成一行,并处理空值
SELECT u.id, COALESCE((SELECT MAX(o.amount) FROM orders o WHERE o.user_id = u.id), 0) AS max_amt
FROM users u;执行行为对照
| 子查询形态 | 执行方式 | 需要注意 |
|---|---|---|
| 不引用外层的常量式子查询 | 通常只计算一次并复用结果 | 结果与数据快照有关,长事务里可能读到旧值 |
| 引用外层列的相关子查询 | 逻辑上逐行求值 | 外层行数大且内表无索引时代价急剧上升 |
| 返回多行的子查询 | 直接报错终止 | 要么加聚合收敛,要么改用连接 |
| 返回零行的子查询 | 结果为 NULL | 参与算术或比较后整行可能变为 NULL |
把标量子查询当成「保证只有一个值」来用时要真正确认唯一性:常见办法是加聚合函数、加排序加数量限制,或直接在业务键上确认唯一约束。仅凭数据现状「现在只有一行」是不够的,数据增长后语句会在最不该出错的时候失败。
相关标量子查询在内表连接列有索引时能跑得不错;一旦内表缺索引,外层每一行都要扫一遍内表,大表上表现为典型的慢查询,改写方向通常是改成左连接加分组聚合,或用窗口函数一次性算出。