子查询在WHERE中需加括号且必须返回单值;IN适用于多值匹配,NOT IN遇NULL失效,应改用NOT EXISTS;FROM中子查询须命名;SELECT中嵌套子查询为相关子查询,性能差,建议用JOIN或CTE替代。

WHERE 里用子查询做动态条件,但别忘了括号和单值约束
子查询最常用的地方就是 WHERE 子句,比如“查工资高于平均值的员工”:SELECT * FROM employees WHERE salary > (SELECT AVG(salary) FROM employees)。这里子查询必须加括号,且返回结果只能是**一行一列**(标量),否则 MySQL 会直接报错 Subquery returns more than 1 row。
- 如果想匹配多个值,改用
IN:WHERE dept_id IN (SELECT id FROM departments WHERE active = 1) - 遇到
NOT IN时要格外小心:只要子查询结果里有任意一个NULL,整个条件就恒为UNKNOWN,查不出任何数据;建议换成NOT EXISTS - 非相关子查询(不依赖外层字段)只执行一次,性能好;相关子查询(如
WHERE salary > (SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id))每行都重算,大数据量时容易变慢
FROM 中写子查询当临时表,别名不是可选而是强制
把子查询放在 FROM 子句中,本质是生成一个派生表(derived table),它像一张临时视图一样供主查询使用。但 MySQL 要求你必须给它起别名,否则语法错误:SELECT * FROM (SELECT user_id, COUNT(*) c FROM orders GROUP BY user_id) AS t —— 注意末尾的 AS t 不可省略。
- 派生表里不能直接写
ORDER BY,除非配合LIMIT(如ORDER BY created_at DESC LIMIT 10) - 适合做中间聚合或过滤,比如先筛出“近30天下单用户”,再统计他们的人均消费
- MySQL 5.7 及以前不支持在派生表中引用外部查询字段(即不能做相关派生表),8.0+ 仍受限,不如 CTE 直观
SELECT 列表里嵌套子查询,只允许返回单个值
在 SELECT 后面加子查询,常用于给每行补一个计算字段,比如“每个用户的订单数”:SELECT id, name, (SELECT COUNT(*) FROM orders WHERE user_id = users.id) AS order_count FROM users。这个子查询必须对每一行都只返回一个值,否则报错。
- 这种写法看似简洁,但实际是**相关子查询**,N 行用户就要执行 N 次子查询,性能隐患明显
- 等价但更高效的做法是用
LEFT JOIN + GROUP BY或 MySQL 8.0+ 的 CTE:WITH user_orders AS (SELECT user_id, COUNT(*) c FROM orders GROUP BY user_id) SELECT u.*, COALESCE(o.c, 0) FROM users u LEFT JOIN user_orders o ON u.id = o.user_id - 别在同一个
SELECT列表里重复写相同子查询(比如两次查部门平均薪资),MySQL 不会自动缓存,会真执行两次
EXISTS 比 IN 更安全,尤其当子查询可能含 NULL 时
判断“是否存在关联记录”时,EXISTS 是比 IN 更可靠的选择。例如查有订单的客户:SELECT name FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id)。它不关心子查询返回什么值,只判断有没有结果集,且天然规避 NULL 导致逻辑失效的问题。
-
EXISTS通常比IN更快,因为找到第一条匹配就终止,而IN往往需穷举全部结果 -
NOT EXISTS是NOT IN的推荐替代方案,避免因子查询含NULL导致整条语句无结果 - 子查询中用
SELECT 1是惯例,语义清晰、无实际数据传输开销
子查询写起来顺手,但真正上线前得盯紧 EXPLAIN 输出里有没有 DEPENDENT SUBQUERY —— 这意味着它正在逐行重执行,而你可能根本没意识到。










