MySQL DQL 子查询详解:从标量、列、行到表子查询的完整实战

1. 什么是子查询

子查询(Subquery)是指出现在其他 SQL 语句(如 SELECT、INSERT、UPDATE、DELETE)中的 SELECT 语句,也被称为内查询(Inner Query)或嵌套查询。外层的主查询(Outer Query)会使用子查询返回的结果作为条件、数据源或字段值,从而完成更复杂的查询需求。

子查询本质上是一张临时生成的虚表,主查询在执行时会把子查询的结果作为输入。理解子查询的分类和适用场景,是掌握 SQL 高级查询能力的关键一步。

2. 子查询的分类

2.1 按子查询出现的位置分类

  • SELECT 后面:仅支持标量子查询,即子查询结果必须是一行一列。
  • WHERE 或 HAVING 后面:支持标量子查询(单行)、列子查询(多行)、行子查询。
  • EXISTS 后面(相关子查询):支持表子查询,用于判断结果是否存在。
  • FROM 后面:子查询结果充当临时表,必须取别名。

2.2 按结果集的行数列数分类

  • 标量子查询:结果只有一行一列。
  • 列子查询:结果只有一列多行。
  • 行子查询:结果有一行多列。
  • 表子查询:结果多行多列。

3. WHERE 或 HAVING 后面的子查询

这是子查询最常用的位置,用于在条件判断中引用其他查询的结果。使用时有以下几个重要特点:

  • 子查询必须放在小括号内。
  • 子查询一般放在条件的右侧。
  • 标量子查询一般搭配单行操作符使用:><>=<=!=<><=>
  • 列子查询一般搭配多行操作符使用:INANYSOMEALL
  • 子查询先于主查询执行,主查询的条件会用到子查询的结果(虚表)。

3.1 标量子查询(单行子查询)

标量子查询返回的结果只有一行一列,通常搭配单行比较操作符使用。下面通过几个经典案例来理解。

案例一:查询谁的工资比 Lex 高。

第一步,先查询 Lex 的工资:

sql 复制代码
SELECT salary
FROM employees
WHERE first_name = 'Lex';

第二步,查询工资大于该结果的员工信息:

sql 复制代码
SELECT *
FROM employees
WHERE salary > (
    SELECT salary
    FROM employees
    WHERE first_name = 'Lex'
);

案例二:返回 job_id 与 141 号员工相同,且 salary 比 143 号员工高的员工名、job_id 和工资。

第一步,查询 141 号员工的 job_id:

sql 复制代码
SELECT job_id
FROM employees
WHERE employee_id = 141;  -- 结果为 ST_CLERK

第二步,查询 143 号员工的工资:

sql 复制代码
SELECT salary
FROM employees
WHERE employee_id = 143;

第三步,组合条件查询目标员工信息:

sql 复制代码
SELECT first_name, job_id, salary
FROM employees
WHERE job_id = (
    SELECT job_id
    FROM employees
    WHERE employee_id = 141
)
AND salary > (
    SELECT salary
    FROM employees
    WHERE employee_id = 143
);

案例三:返回公司工资最少的员工的 last_name、job_id、salary。

第一步,查询公司最低工资:

sql 复制代码
SELECT MIN(salary)
FROM employees;

第二步,查询工资等于该最低工资的员工信息:

sql 复制代码
SELECT last_name, job_id, salary
FROM employees
WHERE salary = (
    SELECT MIN(salary)
    FROM employees
);

案例四:查询最低工资大于 50 号部门最低工资的部门 id 和其最低工资。

第一步,查询 50 号部门的最低工资:

sql 复制代码
SELECT MIN(salary)
FROM employees
WHERE department_id = 50;

第二步,查询每个部门的最低工资:

sql 复制代码
SELECT MIN(salary), department_id
FROM employees
GROUP BY department_id;

第三步,在第二步基础上增加 HAVING 条件,筛选出最低工资大于 50 号部门最低工资的部门:

sql 复制代码
SELECT MIN(salary), department_id
FROM employees
GROUP BY department_id
HAVING MIN(salary) > (
    SELECT MIN(salary)
    FROM employees
    WHERE department_id = 50
);

**注意:非法使用标量子查询。**标量子查询的结果必须是一行一列,如果子查询返回多行,就会导致非法使用。例如下面的写法是错误的:

sql 复制代码
-- 错误示例:子查询返回多行,无法与单值比较
SELECT MIN(salary), department_id
FROM employees
GROUP BY department_id
HAVING MIN(salary) > (
    SELECT salary
    FROM employees
    WHERE department_id = 50
);

因为 department_id = 50 可能对应多个员工的工资,子查询返回多行,而 MIN(salary) 是单值,无法与多行结果直接比较,因此会报错。

3.2 列子查询(多行子查询)

列子查询返回的结果是一列多行,通常搭配多行比较操作符使用:

  • IN / NOT IN:等于列表中的任意一个。
  • ANY / SOME:和子查询返回的某一个值进行比较。
  • ALL:和子查询返回的所有值进行比较。

案例一:返回 location_id 是 1400 或 1700 的部门中的所有员工名。

第一步,查询 location_id 为 1400 或 1700 的部门编号:

sql 复制代码
SELECT DISTINCT department_id
FROM departments
WHERE location_id IN (1400, 1700);

第二步,查询部门号属于上述结果集的员工名:

sql 复制代码
SELECT first_name
FROM employees
WHERE department_id IN (
    SELECT DISTINCT department_id
    FROM departments
    WHERE location_id IN (1400, 1700)
);

案例二:返回其他工种中,比 job_id 为 IT_PROG 工种任一工资低的员工的工号、名、job_id 以及 salary。

第一步,查询 job_id 为 IT_PROG 的所有工资:

sql 复制代码
SELECT DISTINCT salary
FROM employees
WHERE job_id = 'IT_PROG';

第二步,使用 ANY 操作符,查询工资小于上述任意一个工资的员工信息:

sql 复制代码
SELECT employee_id, first_name, job_id, salary
FROM employees
WHERE salary < ANY (
    SELECT DISTINCT salary
    FROM employees
    WHERE job_id = 'IT_PROG'
);

等价写法:小于 IT_PROG 工种的最大工资。

sql 复制代码
SELECT employee_id, first_name, job_id, salary
FROM employees
WHERE salary < (
    SELECT MAX(salary)
    FROM employees
    WHERE job_id = 'IT_PROG'
);

案例三:返回其他工种中,比 job_id 为 IT_PROG 工种所有工资都低的员工的工号、名、job_id 以及 salary。

第一步,查询 job_id 为 IT_PROG 的所有工资:

sql 复制代码
SELECT DISTINCT salary
FROM employees
WHERE job_id = 'IT_PROG';

第二步,使用 ALL 操作符,查询工资小于上述所有工资的员工信息:

sql 复制代码
SELECT employee_id, first_name, job_id, salary
FROM employees
WHERE salary < ALL (
    SELECT DISTINCT salary
    FROM employees
    WHERE job_id = 'IT_PROG'
);

等价写法:小于 IT_PROG 工种的最小工资。

sql 复制代码
SELECT employee_id, first_name, job_id, salary
FROM employees
WHERE salary < (
    SELECT MIN(salary)
    FROM employees
    WHERE job_id = 'IT_PROG'
);

3.3 行子查询(结果一行多列)

行子查询返回的结果是一行多列,通常用于同时匹配多个字段的条件场景。

案例:查询员工编号最小并且工资最高的员工信息。

第一步,查询最小的员工编号:

sql 复制代码
SELECT MIN(employee_id)
FROM employees;

第二步,查询最高工资:

sql 复制代码
SELECT MAX(salary)
FROM employees;

第三步,组合条件查询员工信息:

sql 复制代码
SELECT *
FROM employees
WHERE employee_id = (
    SELECT MIN(employee_id)
    FROM employees
)
AND salary = (
    SELECT MAX(salary)
    FROM employees
);

4. SELECT 后面的子查询

SELECT 后面仅支持标量子查询,即子查询的结果必须是一行一列,通常用于在查询结果中动态生成一个字段值。

案例一:查询每个部门的员工个数。

sql 复制代码
SELECT d.*,
    (SELECT COUNT(*)
     FROM employees e
     WHERE e.department_id = d.department_id) AS 个数
FROM departments d;

这里通过关联子查询,为每个部门动态统计员工数量,结果作为新的一列返回。

案例二:查询员工工号等于 102 的部门名。

sql 复制代码
SELECT (
    SELECT department_name
    FROM departments d
    INNER JOIN employees e
    ON d.department_id = e.department_id
    WHERE e.employee_id = 102
) AS 部门名;

5. FROM 后面的子查询

FROM 后面的子查询会将查询结果充当一张临时表,此时必须为子查询结果取别名,否则会报错。

案例:查询每个部门的平均工资等级。

第一步,查询每个部门的平均工资:

sql 复制代码
SELECT ROUND(AVG(salary)), department_id
FROM employees
GROUP BY department_id;

第二步,将第一步的结果作为临时表,与 job_grades 表连接,筛选平均工资位于对应工资等级区间内的记录:

sql 复制代码
SELECT *
FROM (
    SELECT ROUND(AVG(salary)) AS ag, department_id
    FROM employees
    GROUP BY department_id
) AS ag_dep
INNER JOIN job_grades g
ON ag_dep.ag BETWEEN lowest_sal AND highest_sal;

通过 FROM 子查询,可以把复杂的聚合结果当作一张普通表来参与连接查询,极大提升了 SQL 的表达能力。

6. EXISTS 后面的子查询(相关子查询)

EXISTS 用于判断子查询是否有结果返回,语法为 EXISTS(完整 SQL 语句),返回结果为 1(真)或 0(假)。如果子查询有任意一条记录返回,EXISTS 即为真。

sql 复制代码
SELECT EXISTS(
    SELECT employee_id
    FROM employees
    WHERE salary = 80000
);

EXISTS 子查询通常与主查询的字段相关联,因此也被称为相关子查询。它常用于判断某个记录是否存在,从而决定主查询是否保留该行。

7. 子查询使用注意事项总结

  • 子查询必须放在小括号内,保证优先级清晰。
  • 子查询一般放在比较条件的右侧,符合 SQL 阅读习惯。
  • 标量子查询搭配单行操作符,列子查询搭配多行操作符。
  • FROM 后面的子查询必须取别名,否则语法错误。
  • SELECT 后面的子查询仅支持标量子查询,且必须保证只返回一行一列。
  • EXISTS 子查询关注的是结果是否存在,而不是结果的具体内容。
  • 子查询先于主查询执行,主查询依赖子查询的结果(虚表)进行条件过滤。

8. 结语

子查询是 SQL 查询中非常强大的工具,掌握标量、列、行、表四种子查询的适用场景,能够帮助开发者写出更简洁、更高效的查询语句。在实际开发中,建议结合 EXPLAIN 分析执行计划,在子查询和 JOIN 之间做出合理选择,以兼顾可读性与性能。

相关推荐
harmony&27 分钟前
OpenStack 云平台管理实战:从 Keystone 认证到 Nova 计算调度
数据库·openstack
AI科技先锋报28 分钟前
AI数据资产管理平台全景洞察:从治理基座到智能体应用
大数据·人工智能
betazhou1 小时前
Apache Seatunnel 抽取Oracle数据库测试
数据库·oracle·apache·seatunnel
华奥系科技2 小时前
银发经济浪潮下,智慧养老该如何落地生根
大数据·人工智能
百胜软件@百胜软件2 小时前
百胜软件入选“828精选AI解决方案图谱”,胜券AI助力零售品牌构建专属智能体
大数据·人工智能·零售
大大大大晴天2 小时前
把湖仓一体放进 K8s:Iceberg、Hudi、Paimon 如何重塑云原生大数据架构
大数据
devilnumber3 小时前
Oracle 与 MySQL substr 函数差异总结
java·数据库·mysql·oracle
—Miss. Z—3 小时前
计算机等级考试三级数据库技术—选择题
数据库·mysql