Mysql(7)子查询
2026/4/15 1:52:12 网站建设 项目流程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 子查询
    • select中嵌套子查询
      • select中嵌套子查询
      • where或having中嵌套子查询
      • exists型子查询
      • from中嵌套子查询
      • update中嵌套子查询
    • delete中嵌套子查询
    • 使用子查询复制表结构和数据
  • 通用表达式
      • 一、整体总结
        • 1. 子查询(Subquery)
        • 2. CTE(Common Table Expressions 通用表达式)
        • 3. 递归 CTE(RECURSIVE CTE)
      • 二、各类子查询核心用法总结
        • 1. SELECT 中嵌套子查询
        • 2. WHERE / HAVING 中嵌套子查询
        • 3. FROM 中嵌套子查询(派生表)
        • 4. UPDATE / DELETE 中嵌套子查询
      • 三、高频易错点汇总(重点)

子查询

select中嵌套子查询

子查询:嵌套在另一个SQL语句中的查询。
select语句可以嵌套在另一个select、update、delete、insert、create等语句中。

select中嵌套子查询

--在t_employee表中查询每个人薪资和公司平均薪资的差值--并显示员工薪资和公司平均薪资相差5000元以上的记录 select enameas"姓名",salaryas"薪资",round((select avg(salary)fromt_employee),2)as"全公司平均薪资",round(salary-(select avg(salary)fromt_employee),2)as"差值"fromt_employee whereabs(round(salary-(select avg(salary)fromt_employee),2))>5000;--在t_employee表中查询每个部门平均薪资和公司平均薪资的差值 select did,avg(salary),avg(salary)-(select avg(salary)fromt_employee)fromt_employee group by did;

where或having中嵌套子查询

当子查询结果作为外层另一个SQL的过滤条件,通常把子查询嵌入到where或having中。根据子查询结果的情况,分为如下三种情况:
当子查询的结果是单列单个值,那么可以直接使用比较运算符,如“<”、“<=”、“>”、“>=”、“=”、“!=”等与子查询结果进行比较
当子查询的结果是单列多个值,那么可以使用比较运算符in或not in进行比较
当子查询的结果是单列多个值,还可以使用比较运算符, 如“<”、“<=”、“>”、“>=”、“=”、“!=”等搭配any、all等关键字与查询结果进行比较

子查询结果一列一行

--在t_employee表中查询比全公司平均薪资高的男员工姓名和薪资 select ename,salaryfromt_employee where salary>(select avg(salary)fromt_employee)andgender='男';

子查询,一列多行

--在t_employee表中查询和 白露,谢吉娜 同一部门的员工姓名和电话 select ename,tel,didfromt_employee where didin(select didfromt_employee where ename='白露'orename='谢吉娜');

--在t_employee表中查询薪资比 白露,李诗雨,黄冰茹 三个人的薪资都要高的员工姓名和薪资 select ename,salaryfromt_employee where salary>all(select salaryfromt_employee where enamein('白露','李诗雨','黄冰茹'))

--查询t_employee和t_department表,按部门统计平均工资--显示部门平均工资比全公司的总平均工资高的部门编号、部门名称、部门平均薪资--并按照部门平均薪资升序排列 select t_department.did,dname,avg(salary)fromt_employee right join t_department on t_employee.did=t_department.did group by t_department.did having avg(salary)>(select avg(salary)fromt_employee)order by avg(salary);

exists型子查询

比如下面第一个案例,也就是相当于对t_department的每一行进行判断,其的did是否为null,并且exists中的select是什么事无所谓的,比如看结果

--查询t_employee表中是否存在部门编号为null的员工--如果存在,查询t_department表的部门编号、部门名称 select*fromt_department where exists(select*fromt_employee where didisnull);

同样的道理,也是看t_department的每一行是否满足这个t_employee.did=t_department.did条件,满足的返回

--查询t_department表是否存在与t_employee表相同部门编号的记录--如果存在,查询这些部门的编号和名称 select*fromt_department where exists(select*fromt_employee where t_employee.did=t_department.did);

结果如下,筛选去掉了一个测试部,因为测试部门的id在员工表中不存在

等价于

--查询结果等价于下面的SQL select distinct t_department.*fromt_department inner join t_employee on t_department.did=t_employee.did;

from中嵌套子查询

当子查询结果是多列的结果时,通常将子查询放到from后面,然后采用给子查询结果取别名的方式,把子查询结果当成一张“动态生成的临时表”使用。

若是使用
这种方式

select ename,did,salary,dense_rank()over(partition by did order by salary desc)aspaimingfromt_employee where dense_rank()over(partition by did order by salary desc)<=2;

窗口函数是最后一步进行计算的,所以报错

sql中的执行顺序
FROM
→ WHERE
→ GROUP BY
→ HAVING
→ SELECT ✅(窗口函数在这里算)

# 在t_employee表中查询每个部门中薪资排名前2的员工姓名、部门编号和薪资select*from(select ename,did,salary,dense_rank()over(partition by did order by salary desc)aspaimingfromt_employee)temp where temp.paiming<=2;

update中嵌套子查询

--修改t_employee表中部门编号和测试部部门编号相同的员工薪资为原来薪资的1.5倍 update t_employeesetsalary=salary*1.5where did=(select didfromt_department where dname='测试部');
--修改t_employee表中did为null的员工信息--将他们的did值修改为测试部的部门编号--这种子查询必须是单个值,否则无法赋值 update t_employeesetdid=(select didfromt_department where dname='测试部')where didisnull;

– 当update的表和子查询的表是同一个表时,需要将子查询的结果用临时表的方式表示
– 即再套一层子查询,使得update和最外层的子查询不是同一张表

update t_employeesetdid=(select didfromt_department where dname='测试部')where didisnull;

所以使用下面的办法,嵌套作为一个临时表

--修改t_employee表中李冰冰的薪资值等于孙红梅的薪资值 update t_employeesetsalary=(select salaryfrom(select salaryfromt_employee where ename='孙红梅')temp)where ename='李冰冰';

– 修改t_employee表李冰冰的薪资与她所在部门的平均薪资一样(注意一定要给临时表起别名,否则报错)

update t_employee tsetsalary=(select pingfrom(SELECT avg(salary)pingfromt_employee t2 where t.did=t2.did)tmp)where ename='李冰冰';

delete中嵌套子查询

--从t_employee表中删除测试部的员工记录 deletefromt_employee where did=(select didfromt_department where dname='测试部');
--从t_employee表中删除和李冰冰同一个部门的员工记录 deletefromt_employee where did=(select didfromt_employee where ename='李冰冰');--报错,因为删除和子查询是同一张表

和上面同样的处理方式,设置一个临时表
delete from t_employee
where did =(select did from(SELECT did from t_employee where ename=‘李冰冰’)temp)

使用子查询复制表结构和数据

1)复制表结构
– 仅复制表结构,可以用create语句
create table department like t_department;

2)复制一条或多条记录
– 使用insert语句+子查询,复制数据,此时insert不用写values
insert into department (select * from t_department where did<=3);

3)同时复制表结构和记录
– 同时复制表结构+数据
create table d_department as (select * from t_department);
– 如果select后面是部分字段,复制的新表就只有这一部分字段

通用表达式

通用表达式简称为CTE(Common Table Expressions)。CTE是命名的临时结果集,作用范围是当前语句。CTE可以理解为一个可以复用的子查询,但是和子查询又有区别,一个CTE可以引用其他CTE,CTE还可以是自引用(递归CTE),也可以在同一查询中多次引用,但子查询不可以。

with[recursive]cte_name[(字段名1,字段名2)]as(子查询),cte_name[(字段名1,字段名2)]as(子查询)

– 在t_employee表中查询每个人薪资和公司平均薪资的的差值

SELECT*FROM(SELECT ename AS"员工姓名",salary AS"薪资",AVG(salary)OVER()AS pingjun,ROUND(salary-AVG(salary)OVER(),2)AS"差值"FROM t_employee)t WHERE ROUND(t.`薪资`-t.pingjun,2)>5000;

可以这样写
– 查询薪资低于9000的员工编号,员工姓名,员工薪资,领导编号,领导姓名,领导薪资

但是也可以使用CTE来替代子查询

WITH empas(SELECT*fromt_employee where salary<9000)select emp.salaryas"员工薪资",emp.enameas"员工姓名",emp.midas"领导编号",mgr.enameas"领导姓名",mgr.salaryas"领导薪资"fromemp join t_employee mgr on emp.mid=mgr.eid;

--查询eid为21的员工,和他所有领导,直到最高领导--建表,设置多层领导 create table empas(select eid,ename,salary,tel,`mid`fromt_employee where salary<10000);update empsetmid=19where eid=21;update empsetmid=17where eid=19;update empsetmid=16where eid=17;update empsetmid=15where eid=16;update empsetmid=4where eid=15;update empsetmid=null where eid=4;select*fromemp;withrecursive cteas(select eid,ename,`mid`fromemp where eid=21unionallselect emp.eid,emp.ename,emp.midfromemp join cte on emp.eid=cte.mid where emp.eidisnotnull)select*fromcte;

这里CET循环解释一下
循环有三个条件,起始,递归,终止
首先是起始
select eid,ename,mid
from emp
where eid = 21

循环部分,相当于不断地在结果上进行递加
union all
select emp.id emp.ename,emp.mid
from emp join cte
on cte.mid=emp.eid

终止条件
where emp eid is not null

总结


一、整体总结

1. 子查询(Subquery)

子查询是指嵌套在其他 SQL 语句中的查询,可以出现在SELECTFROMWHEREHAVINGUPDATEDELETE等位置。

核心特点

  • 子查询先执行,其结果作为外层 SQL 的条件或数据源。
  • 根据返回结果不同,可分为:单行单列单列多行多列多行
  • 执行顺序:子查询总是优先于主查询执行

2. CTE(Common Table Expressions 通用表达式)

CTE 是MySQL 8.0+引入的特性,可以理解为可以命名的临时结果集,只在当前语句有效。

与子查询的区别

对比项子查询CTE(通用表达式)
是否需要命名不需要(from中必须)必须命名
可重复使用不可以可以多次引用
是否可相互引用不可以可以(CTE 可以引用其他 CTE)
是否支持递归不支持支持(加RECURSIVE
可读性差(嵌套多层很乱)优秀(结构清晰)

语法结构

WITH[RECURSIVE]cte_name1AS(子查询1),cte_name2AS(子查询2)SELECT...FROMcte_name1...

3. 递归 CTE(RECURSIVE CTE)

递归 CTE 是 CTE 的进阶用法,用于处理层级关系、树形结构(如查找所有上级、所有下属、组织架构等)。

核心组成

  • 种子查询:提供起始点(第一层数据)
  • 递归查询:通过UNION ALL+JOIN自己调用自己
  • 终止条件:当递归查询不再返回结果时自动停止

执行本质:像“滚雪球”一样,一行一行不断累积结果,而不是“在表格后面添加列”。


二、各类子查询核心用法总结

1. SELECT 中嵌套子查询
  • 常用于返回单值(如公司平均薪资)
  • 每次外层查询一行,子查询就会执行一次(性能较差)
2. WHERE / HAVING 中嵌套子查询
  • 单行单列:直接用=><等比较
  • 单列多行:用INANYALL
  • HAVING中可使用聚合函数,WHERE中不能
3. FROM 中嵌套子查询(派生表)
  • 子查询结果当作临时表使用,必须给别名
  • 常用于窗口函数后筛选(因为窗口函数在SELECT中最后计算)
4. UPDATE / DELETE 中嵌套子查询
  • 更新同一张表时,容易出现You can't specify target table错误
  • 解决办法:把子查询再包一层,成为临时表

三、高频易错点汇总(重点)

以下是这篇笔记中最容易出错的地方,按严重程度排序:

序号易错点具体表现正确做法
1窗口函数写在 WHERE 中WHERE dense_rank() OVER(...) <= 2必须包一层子查询,在外层用WHERE筛选
2UPDATE/DELETE 与子查询操作同一张表You can't specify target table子查询外面再包一层临时表
3HAVING 中使用 SELECT 的别名HAVING 差值 > 5000外层用子查询,或者重复写表达式
4递归 CTE 方向写反ON emp.eid = cte.mid(想找上级却写成向下)向上找领导用ON e.eid = cte.mid,向下找下属用ON e.mid = cte.eid
5递归 CTE 没有终止条件导致Recursive query aborted after 1001 iterations加上level字段 +WHERE cte.level < 20限制
6子查询返回多行却用=WHERE did = (SELECT did ...)返回多行时报错改用IN
7FROM 子查询不写别名FROM (SELECT ...)没给别名必须写别名,如FROM (...) t
8EXISTS 子查询写法混淆EXISTS当成返回具体值使用EXISTS只关心“有没有结果”,不关心内容
9CTE 中忘记写 RECURSIVE递归查询无法生效递归必须写WITH RECURSIVE
10多表查询不加表别名导致 ambiguousColumn 'eid' is ambiguous所有字段都加上表别名(如emp.eidmgr.eid

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询