讲解

子查询是嵌在另一条 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。