MySQL数据库:复合查询

文章目录

MySQL复合查询入门指南:从单表"单打独斗"到多表"组队开黑"

哈喽各位SQL新手村的玩家!当你敲出 select * from emp 能顺利出结果的时候,是不是觉得自己SQL已经入门了?

别急着高兴,真实业务里的数据从来不会乖乖待在一张表里。员工信息在EMP表,部门信息在DEPT表,工资等级在SALGRADE表......就像你查一个员工的信息,总不能只知道他叫啥,还得知道他哪个部门、工资几级、领导是谁吧?

这就是咱们今天的主角------MySQL复合查询。说白了就是把单表查询的招式组合起来,玩出多表联动、嵌套套娃、结果合并的花活。咱们就用经典的公司员工表举例,全程人话+案例+真实运行输出,保证看完就能上手。

前置:本次用到的三张基础表

先把测试数据摆出来,所有例子的结果都从这几张表里来,方便大家对照。

员工表 EMP(14名员工)

empno ename job mgr hiredate sal comm deptno
7369 SMITH CLERK 7902 1980-12-17 800.00 NULL 20
7499 ALLEN SALESMAN 7698 1981-02-20 1600.00 300.00 30
7521 WARD SALESMAN 7698 1981-02-22 1250.00 500.00 30
7566 JONES MANAGER 7839 1981-04-02 2975.00 NULL 20
7654 MARTIN SALESMAN 7698 1981-09-28 1250.00 1400.00 30
7698 BLAKE MANAGER 7839 1981-05-01 2850.00 NULL 30
7782 CLARK MANAGER 7839 1981-06-09 2450.00 NULL 10
7788 SCOTT ANALYST 7566 1987-04-19 3000.00 NULL 20
7839 KING PRESIDENT NULL 1981-11-17 5000.00 NULL 10
7844 TURNER SALESMAN 7698 1981-09-08 1500.00 0.00 30
7876 ADAMS CLERK 7788 1987-05-23 1100.00 NULL 20
7900 JAMES CLERK 7698 1981-12-03 950.00 NULL 30
7902 FORD ANALYST 7566 1981-12-03 3000.00 NULL 20
7934 MILLER CLERK 7782 1982-01-23 1300.00 NULL 10

部门表 DEPT(4个部门)

deptno dname loc
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON

工资等级表 SALGRADE(5个等级)

grade losal hisal
1 700 1200
2 1201 1400
3 1401 2000
4 2001 3000
5 3001 9999

一、温故知新:先掏出你的单表查询家底

先快速过一遍单表查询的核心操作,这是复合查询的基本功。就像打怪前先检查装备:

1. 条件拼接:and/or 组合拳

需求:找「工资高于500 或者 岗位是经理」,并且姓名首字母是大写J的员工

sql 复制代码
select * from EMP 
where (sal>500 or job='MANAGER') 
  and ename like 'J%';

运行输出:

empno ename job mgr hiredate sal comm deptno
7566 JONES MANAGER 7839 1981-04-02 2975.00 NULL 20
7900 JAMES CLERK 7698 1981-12-03 950.00 NULL 30

💡 小提醒:and 的优先级比 or 高,不确定优先级就加括号,准没错。

2. 排序:order by 的升降之道

需求:按部门号升序,同部门内按工资降序排序

sql 复制代码
select empno, ename, deptno, sal 
from EMP 
order by deptno, sal desc;

运行输出:

empno ename deptno sal
7839 KING 10 5000.00
7782 CLARK 10 2450.00
7934 MILLER 10 1300.00
7788 SCOTT 20 3000.00
7902 FORD 20 3000.00
7566 JONES 20 2975.00
7876 ADAMS 20 1100.00
7369 SMITH 20 800.00
7698 BLAKE 30 2850.00
7499 ALLEN 30 1600.00
7844 TURNER 30 1500.00
7521 WARD 30 1250.00
7654 MARTIN 30 1250.00
7900 JAMES 30 950.00

还可以给列起别名再排序,比如算年薪再降序:

sql 复制代码
select ename, sal*12 + ifnull(comm,0) as '年薪' 
from EMP 
order by 年薪 desc;

运行输出:

ename 年薪
KING 60000
SCOTT 36000
FORD 36000
JONES 35700
BLAKE 34200
CLARK 29400
MARTIN 29000
ALLEN 22800
TURNER 18000
MILLER 15600
WARD 15000
ADAMS 13200
JAMES 11400
SMITH 9600

3. 分组统计:group by + having

分组是统计利器,比如每个部门的平均工资和最高工资:

sql 复制代码
select deptno, 
       format(avg(sal), 2) as 平均工资, 
       max(sal) as 最高工资 
from EMP 
group by deptno;

运行输出:

deptno 平均工资 最高工资
10 2916.67 5000.00
20 2175.00 3000.00
30 1566.67 2850.00

⚠️ 新手必踩坑:where 是分组前 过滤行,having 是分组后 过滤结果。

比如找平均工资低于2000的部门,只能用having:

sql 复制代码
select deptno, avg(sal) as avg_sal 
from EMP 
group by deptno 
having avg_sal < 2000;

运行输出:

deptno avg_sal
30 1566.6667

再拓展一个:显示每种岗位的雇员总数、平均工资

sql 复制代码
select job,
       count(*) as 人数,
       format(avg(sal),2) as 平均工资
from EMP 
group by job;

运行输出:

job 人数 平均工资
CLERK 4 1037.50
SALESMAN 4 1400.00
MANAGER 3 2758.33
ANALYST 2 3000.00
PRESIDENT 1 5000.00

二、多表查询:把几张表"焊"在一起查

单表再强也架不住数据分散。真实开发里,80%的场景都要查2张及以上的表,这就是多表查询。

1. 先讲个"恐怖故事":笛卡尔积

先看个错误示范:如果直接查两张表,不加任何条件:

sql 复制代码
select * from EMP, DEPT;

运行结果 :14个员工 × 4个部门 = 56条记录 ,每个员工都会和所有部门匹配一遍,这就是笛卡尔积。

这就好比公司14个员工,4个部门,让每个员工都去每个部门"报到"一次,纯纯无效数据,数据量大的时候直接把库跑崩。

2. 正确打开方式:等值连接

怎么避免笛卡尔积?加连接条件 !

员工和部门靠 deptno(部门号)关联,所以我们只要「员工表的部门号 = 部门表的部门号」的记录:

sql 复制代码
select e.ename, e.sal, d.dname 
from EMP e, DEPT d 
where e.deptno = d.deptno;

运行输出(共14条):

ename sal dname
SMITH 800.00 RESEARCH
ALLEN 1600.00 SALES
WARD 1250.00 SALES
JONES 2975.00 RESEARCH
MARTIN 1250.00 SALES
BLAKE 2850.00 SALES
CLARK 2450.00 ACCOUNTING
SCOTT 3000.00 RESEARCH
KING 5000.00 ACCOUNTING
TURNER 1500.00 SALES
ADAMS 1100.00 RESEARCH
JAMES 950.00 SALES
FORD 3000.00 RESEARCH
MILLER 1300.00 ACCOUNTING

📌 开发小技巧:给表起别名(e代表EMP,d代表DEPT),代码更简洁,也避免字段重名报错。

💡 补充:更规范的 JOIN 写法

上面逗号分隔的是"隐式内连接",现在业界更推荐用标准的 join ... on 写法,语义更清晰,也方便后续加左连接、右连接:

sql 复制代码
select e.ename, e.sal, d.dname 
from EMP e
inner join DEPT d 
on e.deptno = d.deptno;

inner join 也可以简写成 join,效果完全一样。

3. 更多场景练习

场景1:查10号部门的部门名、员工名和工资
sql 复制代码
select e.ename, e.sal, d.dname 
from EMP e, DEPT d 
where e.deptno = d.deptno 
  and d.deptno = 10;

运行输出:

ename sal dname
CLARK 2450.00 ACCOUNTING
KING 5000.00 ACCOUNTING
MILLER 1300.00 ACCOUNTING
场景2:查员工的工资等级

工资等级表SALGRADE里存了每个等级的薪资区间,用 between and 就能匹配每个员工的工资级别:

sql 复制代码
select e.ename, e.sal, s.grade 
from EMP e, SALGRADE s 
where e.sal between s.losal and s.hisal;

运行输出:

ename sal grade
SMITH 800.00 1
ADAMS 1100.00 1
JAMES 950.00 1
WARD 1250.00 2
MARTIN 1250.00 2
MILLER 1300.00 2
ALLEN 1600.00 3
TURNER 1500.00 3
JONES 2975.00 4
BLAKE 2850.00 4
CLARK 2450.00 4
SCOTT 3000.00 4
FORD 3000.00 4
KING 5000.00 5

三、自连接:自己和自己玩"找领导"

有个很经典的场景:员工表里面存了每个人的领导编号(mgr),但领导本身也是员工,也在这张表里。怎么查一个员工的领导是谁?

这就要用到自连接------一张表当成两张用,自己连自己。

例子:找员工FORD的上级领导

写法1:子查询(笨办法)

先查FORD的领导编号,再用编号查姓名:

sql 复制代码
select empno, ename 
from emp 
where empno = (select mgr from emp where ename='FORD');

运行输出:

empno ename
7566 JONES
写法2:自连接(优雅写法)

给表起两个别名,worker 当员工表,leader 当领导表:

sql 复制代码
select leader.empno, leader.ename 
from emp leader, emp worker 
where leader.empno = worker.mgr 
  and worker.ename = 'FORD';

运行输出:

empno ename
7566 JONES

原理很简单:把一张表拆成"员工"和"领导"两个角色,通过「员工的领导号 = 领导的员工号」关联,就把上下级关系查出来了。

🎯 适用场景:树形结构、上下级关系、同表内的关联数据,都用自连接搞定。


四、子查询:SQL界的"套娃"艺术

子查询,就是select里面还套着select,也叫嵌套查询。就像拆快递,大盒子里还有小盒子。

根据返回结果不同,分好几种类型:

1. 单行子查询:只返回一个值

子查询结果只有一行一列,可以用 = > < 这些比较运算符。

例子1:查询和SMITH同一个部门的所有员工

sql 复制代码
select empno, ename, deptno, sal 
from EMP 
where deptno = (select deptno from EMP where ename='SMITH');

jieshi

运行输出:

empno ename deptno sal
7369 SMITH 20 800.00
7566 JONES 20 2975.00
7788 SCOTT 20 3000.00
7876 ADAMS 20 1100.00
7902 FORD 20 3000.00

例子2:找工资最高的员工姓名和岗位

sql 复制代码
select ename, job 
from EMP 
where sal = (select max(sal) from EMP);

解释:这条SQL是单行子查询,会先在员工表中查出SMITH的部门号20,再用这个部门号作为过滤条件,查询员工表里所有部门号为20的员工;注意等号要求子查询只能返回单个值,若子查询返回多条数据就不能用等号,需要改用in,该语句等价于自连接查询,作用就是找出和SMITH同一个部门的全部员工。

运行输出:

ename job
KING PRESIDENT

⚠️ 注意:单行子查询必须保证只返回一行,不然SQL直接报错。

2. 多行子查询:返回一堆值

子查询返回多行一列,这时候就不能用 = 了,要用 in、all、any。

① in:在集合里就行

需求:查询和10号部门岗位相同的员工,不包含10号部门自己

sql 复制代码
select ename, job, sal, deptno 
from emp 
where job in (select distinct job from emp where deptno=10) 
  and deptno <> 10;

运行输出:

ename job sal deptno
JONES MANAGER 2975.00 20
BLAKE MANAGER 2850.00 30
SMITH CLERK 800.00 20
ADAMS CLERK 1100.00 20
JAMES CLERK 950.00 30

in 就是"只要岗位在子查询的结果里,就算符合条件"。

② all:比所有都...

需求 :工资比30部门所有员工都高的人(碾压全场)

sql 复制代码
select ename, sal, deptno 
from EMP 
where sal > all(select sal from EMP where deptno=30);

运行输出:

ename sal deptno
JONES 2975.00 20
SCOTT 3000.00 20
KING 5000.00 10
FORD 3000.00 20

可以理解成:比30部门最高工资(2850)还高才算数,真正的降维打击。

③ any:比任意一个都...

需求 :工资比30部门任意一个员工高的人(打赢一个就算赢)

sql 复制代码
select ename, sal, deptno 
from EMP 
where sal > any(select sal from EMP where deptno=30);

运行输出(共13条,展示全部):

ename sal deptno
ALLEN 1600.00 30
WARD 1250.00 30
JONES 2975.00 20
MARTIN 1250.00 30
BLAKE 2850.00 30
CLARK 2450.00 10
SCOTT 3000.00 20
KING 5000.00 10
TURNER 1500.00 30
ADAMS 1100.00 20
FORD 3000.00 20
MILLER 1300.00 10

只要比30部门最低工资(950)高就算符合条件,除了工资800的SMITH,其他人都满足。

📌 记忆口诀:

  • all:全部都要满足,碾压全场
  • any:满足一个就行,菜鸡里拔将军

3. 多列子查询:一次返回多列

子查询还能一次返回多个列,一起做匹配。

需求:找部门和岗位都和SMITH完全一样的员工(不含SMITH本人)

sql 复制代码
select ename 
from EMP 
where (deptno, job) = (select deptno, job from EMP where ename='SMITH') 
  and ename <> 'SMITH';

运行输出:

ename
ADAMS

把两个字段打包成一组去对比,比分开写两个子查询简洁多了。

4. from子句里的子查询:把查询结果当表用

子查询不止能写在where后面,还能写在from后面,当成一张临时表来用。这是进阶必学技巧。

例子1:找出工资高于自己部门平均工资的员工

思路:

  1. 先算出每个部门的平均工资,得到一张临时表
  2. 把员工表和这张临时表关联,对比工资
sql 复制代码
select e.ename, e.deptno, e.sal, format(tmp.asal,2) as 部门平均工资
from EMP e, 
     (select avg(sal) asal, deptno dt from EMP group by deptno) tmp
where e.deptno = tmp.dt 
  and e.sal > tmp.asal;

运行输出:

ename deptno sal 部门平均工资
KING 10 5000.00 2916.67
JONES 20 2975.00 2175.00
SCOTT 20 3000.00 2175.00
FORD 20 3000.00 2175.00
ALLEN 30 1600.00 1566.67
BLAKE 30 2850.00 1566.67
例子2:查找每个部门工资最高的人的姓名、工资、部门、最高工资
sql 复制代码
select e.ename, e.sal, e.deptno, tmp.ms as 部门最高工资
from EMP e, 
     (select max(sal) ms, deptno from EMP group by deptno) tmp 
where e.deptno = tmp.deptno 
  and e.sal = tmp.ms;

运行输出:

ename sal deptno 部门最高工资
BLAKE 2850.00 30 2850.00
KING 5000.00 10 5000.00
SCOTT 3000.00 20 3000.00
FORD 3000.00 20 3000.00

注意:20部门有SCOTT和FORD两个人都是3000的最高工资,所以会返回两条。

例子3:显示每个部门的信息(部门名、编号、地址)和人员数量

写法1:多表关联 + 分组

sql 复制代码
select d.dname, d.deptno, d.loc, count(*) as 部门人数
from EMP e, DEPT d
where e.deptno = d.deptno
group by d.deptno, d.dname, d.loc;

写法2:子查询当临时表

sql 复制代码
select d.deptno, d.dname, d.loc, tmp.mycnt as 部门人数
from DEPT d, 
     (select count(*) mycnt, deptno from EMP group by deptno) tmp
where d.deptno = tmp.deptno;

两种写法运行输出一致:

dname deptno loc 部门人数
ACCOUNTING 10 NEW YORK 3
RESEARCH 20 DALLAS 5
SALES 30 CHICAGO 6

⚠️ 注意:这两种写法都不会显示40号部门,因为40部门没有员工。如果想让没人的部门也显示(人数为0),需要用左外连接,后面进阶篇再讲。


五、合并查询:把多个结果"拼"起来

实际开发中,有时候需要把两个select的结果上下拼在一起,这时候就用集合操作符 union 和 union all。

1. union:去重合并

union 会自动去掉结果集中的重复行。

需求:找出工资大于2500 或者 职位是MANAGER的员工

sql 复制代码
select ename, sal, job from EMP where sal>2500 
union
select ename, sal, job from EMP where job='MANAGER';

运行输出:

ename sal job
JONES 2975.00 MANAGER
BLAKE 2850.00 MANAGER
SCOTT 3000.00 ANALYST
KING 5000.00 PRESIDENT
FORD 3000.00 ANALYST
CLARK 2450.00 MANAGER

JONES和BLAKE同时满足两个条件,但结果里只出现一次,因为union自动去重了。

2. union all:不去重合并

union all 就是简单粗暴地把两个结果拼在一起,不做去重,性能比union高。

sql 复制代码
select ename, sal, job from EMP where sal>2500 
union all
select ename, sal, job from EMP where job='MANAGER';

运行输出:

ename sal job
JONES 2975.00 MANAGER
BLAKE 2850.00 MANAGER
SCOTT 3000.00 ANALYST
KING 5000.00 PRESIDENT
FORD 3000.00 ANALYST
JONES 2975.00 MANAGER
BLAKE 2850.00 MANAGER
CLARK 2450.00 MANAGER

可以看到JONES和BLAKE各出现了两次,总共8条记录。

📌 开发经验:

  • 不需要去重的场景优先用 union all,性能好很多
  • 使用union/union all时,两个select的列数、列类型必须对应一致

六、新手避坑总结

  1. 多表查询必加连接条件,不然会产生笛卡尔积,数据量爆炸
  2. 分组后过滤用having,不要用where筛分组结果
  3. 单行子查询用=,多行用in/all/any,别搞混运算符
  4. union去重,union all不去重,优先用union all性能更好
  5. 子查询可以当临时表用,from后面套select是进阶必备技巧

复合查询看着花哨,本质还是单表查询的组合拳。把每个基础招式练熟,多套几层、多连几张表,都是一样的逻辑。

相关推荐
Omics Pro1 小时前
北理工李荣华:AI虚拟细胞证据多模态推理基准
数据库·人工智能·算法·机器学习·自然语言处理
一个天蝎座 白勺 程序猿1 小时前
IoTDB集群扩容:从慌乱到从容的经验分享
数据库·wpf·时序数据库·iotdb
FYKJ_20101 小时前
SpringSecurity教室智能预约系统73972-计算机课程设计、毕业设计
java·vue.js·spring boot·python·mysql·spring·django
写后端的胖头鱼1 小时前
详解索引下推
数据库·索引失效·索引·icp·索引下推
要开心吖ZSH1 小时前
系统并发与 QPS 上限:从 200 个线程到每秒几万请求
redis·mysql·tomcat·并发·连接池·qps
Arcai10242 小时前
PostgreSQL数据备份和恢复完全指南
数据库·postgresql·ffmpeg·备份一体机·备份软件·smartstor·智备科技
FYKJ_20102 小时前
springboot游泳馆管理系统79222-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·mysql·django·课程设计
闲云自留地4 小时前
主库倒下 30 秒自动扶正:MySQL MHA 高可用实战
数据库·mysql
许彰午12 小时前
01-Oracle数据库简介与版本选择
数据库·oracle