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 后面的子查询
这是子查询最常用的位置,用于在条件判断中引用其他查询的结果。使用时有以下几个重要特点:
- 子查询必须放在小括号内。
- 子查询一般放在条件的右侧。
- 标量子查询一般搭配单行操作符使用:
>、<、>=、<=、!=、<>、<=>。 - 列子查询一般搭配多行操作符使用:
in、any、some、all。 - 子查询先于主查询执行,主查询的条件会用到子查询的结果(虚表)。
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子查询的资料请关注代码网其它相关文章!
发表评论