讲解
子查询是嵌在另一条 SQL 里的查询,用括号包起来,可以出现在 WHERE、SELECT、FROM 等位置。按结果形态分三类:标量子查询返回单个值(一行一列),可以和 =、> 直接比较;表子查询返回多行多列,放在 FROM 里当临时表用;IN 后面的子查询返回一列多个值,判断「是否在其中」。
典型场景:「高于平均客单价的订单」——平均价要先算出来才能比较,WHERE amount > (SELECT AVG(amount) FROM orders) 一条搞定。数据库先执行括号里的子查询,把结果代入外层再执行。「买过编程类课程的学生」则用 IN:WHERE id IN (SELECT student_id FROM orders JOIN courses ...),比一层层 join 再去重更直观。
FROM 里的子查询(派生表)让复杂统计可以分两步走:先在子查询里按学生聚合出消费总额,再在外层对这份「中间结果」继续筛选、排序。子查询还有「相关」与「不相关」之分:不相关子查询独立执行一次即可;相关子查询引用外层的列,每行都要重算一次,功能强但可能很慢——能改写成 JOIN 的往往更快。
示例
标量子查询:金额高于平均客单价的订单:
SELECT id, amount FROM orders WHERE amount > (SELECT AVG(amount) FROM orders) ORDER BY amount DESC;
IN 子查询:买过「编程」类课程的学生姓名:
SELECT name FROM students WHERE id IN (SELECT o.student_id FROM orders AS o JOIN courses AS c ON o.course_id = c.id WHERE c.category = '编程');
派生表:先按学生算总消费,再筛出超过 60 元的:
SELECT student_id, total FROM (SELECT student_id, SUM(amount) AS total FROM orders WHERE status = 'paid' GROUP BY student_id) AS t WHERE t.total > 60 ORDER BY total DESC;
常见坑
- 标量子查询返回多行:WHERE amount = (SELECT ...) 里子查询一旦返回多行就报错。用比较运算符前先确认子查询最多返回一行,多行场景改用 IN。
- NOT IN 子查询混入 NULL:子查询结果里只要有一个 NULL,NOT IN 就整体查不出任何行(IN 章节的陷阱在这里最常踩)。子查询里加 WHERE col IS NOT NULL 防御,或改用 NOT EXISTS。
- 相关子查询的性能:外层每行都重算一遍子查询,大表上可能从毫秒变分钟。先用小数据验证正确性,大数据量优先考虑 JOIN 改写。
- 在派生表里省略别名:FROM (SELECT ...) 后面必须给这个临时结果起别名(如 AS t),否则多数数据库直接报语法错误。
小结
子查询按形态分标量、列、表三种,可嵌在 WHERE、IN、FROM 中;标量子查询必须单行单列,NOT IN 警惕 NULL。下一节学习合并两个查询的结果:UNION。