MySQL DQL子查询的完整实战

 更新时间:2026年09月01日 09:54:44   作者:爱吃火鸡面呀  
本文用5个实战案例带你彻底搞懂标量、列、行、表子查询的用法,从WHERE到SELECT再到FROM和EXISTS,一次性掌握子查询的分类、语法和注意事项,帮你写出更高效的SQL查询,需要的朋友可以参考下

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 的工资:

SELECT salary
FROM employees
WHERE first_name = 'Lex';

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

SELECT *
FROM employees
WHERE salary > (
    SELECT salary
    FROM employees
    WHERE first_name = 'Lex'
);

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

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

SELECT job_id
FROM employees
WHERE employee_id = 141;  -- 结果为 ST_CLERK

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

SELECT salary
FROM employees
WHERE employee_id = 143;

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

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。

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

SELECT MIN(salary)
FROM employees;

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

SELECT last_name, job_id, salary
FROM employees
WHERE salary = (
    SELECT MIN(salary)
    FROM employees
);

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

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

SELECT MIN(salary)
FROM employees
WHERE department_id = 50;

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

SELECT MIN(salary), department_id
FROM employees
GROUP BY department_id;

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

SELECT MIN(salary), department_id
FROM employees
GROUP BY department_id
HAVING MIN(salary) > (
    SELECT MIN(salary)
    FROM employees
    WHERE department_id = 50
);

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

-- 错误示例:子查询返回多行,无法与单值比较
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 的部门编号:

SELECT DISTINCT department_id
FROM departments
WHERE location_id IN (1400, 1700);

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

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 的所有工资:

SELECT DISTINCT salary
FROM employees
WHERE job_id = 'IT_PROG';

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

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 工种的最大工资。

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 的所有工资:

SELECT DISTINCT salary
FROM employees
WHERE job_id = 'IT_PROG';

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

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 工种的最小工资。

SELECT employee_id, first_name, job_id, salary
FROM employees
WHERE salary < (
    SELECT MIN(salary)
    FROM employees
    WHERE job_id = 'IT_PROG'
);

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

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

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

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

SELECT MIN(employee_id)
FROM employees;

第二步,查询最高工资:

SELECT MAX(salary)
FROM employees;

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

SELECT *
FROM employees
WHERE employee_id = (
    SELECT MIN(employee_id)
    FROM employees
)
AND salary = (
    SELECT MAX(salary)
    FROM employees
);

4. SELECT 后面的子查询

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

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

SELECT d.*,
    (SELECT COUNT(*)
     FROM employees e
     WHERE e.department_id = d.department_id) AS 个数
FROM departments d;

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

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

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 后面的子查询会将查询结果充当一张临时表,此时必须为子查询结果取别名,否则会报错。

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

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

SELECT ROUND(AVG(salary)), department_id
FROM employees
GROUP BY department_id;

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

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 即为真。

SELECT EXISTS(
    SELECT employee_id
    FROM employees
    WHERE salary = 80000
);

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

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

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

8. 结语

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

以上就是MySQL DQL子查询的完整实战的详细内容,更多关于MySQL DQL子查询的资料请关注脚本之家其它相关文章!

相关文章

  • mysql 索引详细介绍

    mysql 索引详细介绍

    这篇文章主要介绍了mysql 索引详细介绍的相关资料,需要的朋友可以参考下
    2016-09-09
  • MySQL多表查询与7种JOINS的实现举例

    MySQL多表查询与7种JOINS的实现举例

    最近学习了多表查询,对此做一些笔记的整理,下面这篇文章主要给大家介绍了关于MySQL多表查询与7种JOINS实现的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-02-02
  • MySQL 触发器的基础操作(六)

    MySQL 触发器的基础操作(六)

    这篇文章主要为大家详细介绍了MySQL 触发器的基础操作,告诉大家什么是MySQL触发器,如何查看触发器,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • mysql 基础教程之库与表的详解

    mysql 基础教程之库与表的详解

    这篇文章主要介绍了mysql 基础教程之库与表的详解的相关资料,需要的朋友可以参考下
    2017-01-01
  • 简述Redis和MySQL的区别

    简述Redis和MySQL的区别

    这篇文章主要介绍了简述Redis和MySQL的区别,小编觉得挺不错的,这里给大家分享下,需要的朋友可以了解。
    2017-10-10
  • 如何使Mysql自动生成序号列,序号自动增长问题

    如何使Mysql自动生成序号列,序号自动增长问题

    这篇文章主要介绍了如何使Mysql自动生成序号列,序号自动增长问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 一文带你搞懂mysql中的三种数据读取方式

    一文带你搞懂mysql中的三种数据读取方式

    在与MySQL数据库交互时,数据的读取方式有多种选择,每种方式都有其独特的原理、优势和劣势,本文将对这三种读取方式进行详细介绍,需要的可以参考下
    2024-12-12
  • MySQL 不允许从远程访问的解决方法

    MySQL 不允许从远程访问的解决方法

    MySQL 不允许从远程访问的原因有很多除了下面的方法,还有需要看服务器安全设置禁止访问本机的3306端口。
    2010-03-03
  • MySQL性能优化之如何从底层原理到实战落地

    MySQL性能优化之如何从底层原理到实战落地

    本文将融合底层理论与实战经验,构建“原理认知-问题定位-优化实施-工程保障”的完整体系,助力开发者实现MySQL性能的精准提升,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • MySQL中的max()函数使用教程

    MySQL中的max()函数使用教程

    这篇文章主要介绍了MySQL中的max()函数使用教程,是学习MySQL入门的基础知识,需要的朋友可以参考下
    2015-05-05

最新评论