MySQL从零开始了解数据库开发之复合查询

 更新时间:2025年11月04日 16:47:23   作者:叫我龙翔  
本文介绍了MySQL数据库开发中的复合查询技术,包括多表查询、自连接和子查询三种方法,示例演示了从员工表和部门表中联合查询员工姓名、工资及部门名称, 本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

今天我们来学习复合查询,复合查询包括以下内容:多表查询,子查询,自连接。理解复合查询就一句话:mysql一切皆为表。

1 多表查询

多表查询顾名思义是从多张表中查询数据,而之前都是从单表读取的。在实际开发过程中,同一模块的数据会存放在很多中,查询时进行多表查询时非常频繁的操作

本节我们用一个简单的公司管理系统,有三张表EMP,DEPT,SALGRADE来演示如何进行多表查询。
表结构:

现在我们要显示雇员名、雇员工资以及所在部门的名字, 因为雇员名,雇员工资和所在部门数据分别来自EMP和DEPT表,所以这里需要多表查询。

执行语句

select * from EMP , DEPT;

我们会得到一个大表:

这个表是由笛卡尔积得到的,EMP表中的每个数据都与DEPT的数据进行一次匹配整合。

笛卡尔积得到的这张大表就是我们进行多表查询的基础,这个操作将两张表的数据整合到一张表里。接下来我们可以通过增加where条件限制,获取有效的数据:

接下来就可以通过指定列查询需要的数据了!

同样下面几个例子也就可以快速写出来

  • 显示部门号为10的部门名,员工名和工资
select  EMP.ENAME,DEPT.DNAME , EMP.SAL from EMP , DEPT where EMP.DEPTNO = DEPT.DEPTNO and EMP.DEPTNO = 10;
  • 显示各个员工的姓名,工资,及工资级别
mysql> select  *  from EMP , SALGRADE WHERE EMP.SAL between LOSAL and HISAL;

2 自连接

刚才我们是对两张不同的表进行笛卡尔积,然后通过大表的数据进行读取 ,那么对于同一张表也可以进行笛卡尔积!
注意需要对这个表取一下别名,不然会认为是同一张表语法而报错

这样就得到了同一张表的笛卡尔积的结果,称之为自连接。那么什么场景需要是需要自连接来解决的呢?我们来个场景:

  • 显示员工Ford的上级领导的编号和姓名(mgr代表员工领导的编号)

因为领导也是员工,所以都储存在员工表中。先分析一下首先先选取匹配的领导,然后找到员工Ford;

mysql> select t1.ename , t2.ename '领导', t2.empno from emp t1 , emp t2 where t1.mgr = t2.empno and t1.ename = 'FORD';
+-------+--------+-------+
| ename | 领导   | empno |
+-------+--------+-------+
| FORD  | JONES  |  7566 |
+-------+--------+-------+
1 row in set (0.00 sec)

这就就成功筛选出来了;

3 子查询

子查询用于多表查询,多表查询问题的本质就是想办法将多表转换为单表

3.1 单行子查询

子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询。区别于上面连接构建笛卡尔积的方法,子查询是通过构建子查询来完成查询任务。

子查询的核心逻辑是:mysql万物皆为表,查询结果也是一种表。

比如下面几个场景

  • 查询SMITH同一部门的员工

首先需要向找到SMITH是哪一个部门,然后根据这个部门去查询员工

mysql> select * from emp where ename = 'SMITH';
+-------+-------+-------+------+------------+--------+------+--------+
| empno | ename | job   | mgr  | hiredate   | sal    | comm | deptno |
+-------+-------+-------+------+------------+--------+------+--------+
|  7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL |     20 |
+-------+-------+-------+------+------------+--------+------+--------+
1 row in set (0.00 sec)
mysql> select deptno  from emp where ename = 'SMITH';
+--------+
| deptno |
+--------+
|     20 |
+--------+
1 row in set (0.00 sec)
mysql> select * from emp where deptno = (select deptno  from emp where ename = 'SMITH');
+-------+-------+---------+------+------------+---------+------+--------+
| empno | ename | job     | mgr  | hiredate   | sal     | comm | deptno |
+-------+-------+---------+------+------------+---------+------+--------+
|  7369 | SMITH | CLERK   | 7902 | 1980-12-17 |  800.00 | NULL |     20 |
|  7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL |     20 |
|  7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL |     20 |
|  7876 | ADAMS | CLERK   | 7788 | 1987-05-23 | 1100.00 | NULL |     20 |
|  7902 | FORD  | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL |     20 |
+-------+-------+---------+------+------------+---------+------+--------+
5 rows in set (0.00 sec)

这样我们就通过对子查询查询出来的部门去找到了目标员工。

  • 查询与10号部门的工作岗位相同的雇员的名字,岗位,工资,部门名称,但是不包含10自己的

我们来拆解一下查询内容:

  1. 首先先找到10号部门的工作岗位
  2. 然后根据工作岗位去查询雇员信息
  3. 查询的雇员信息与部门表做笛卡尔积找到部门名称

这样一步一步就可以:

mysql> select job from emp where deptno = 10;
+-----------+
| job       |
+-----------+
| MANAGER   |
| PRESIDENT |
| CLERK     |
+-----------+
3 rows in set (0.00 sec)
mysql> select * from emp where job in (select distinct job from emp where deptno = 10);
+-------+--------+-----------+------+------------+---------+------+--------+
| empno | ename  | job       | mgr  | hiredate   | sal     | comm | deptno |
+-------+--------+-----------+------+------------+---------+------+--------+
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 | NULL |     20 |
|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 | NULL |     20 |
|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 | NULL |     30 |
|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 | NULL |     10 |
|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL |     10 |
|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 | NULL |     20 |
|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 | NULL |     30 |
|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 | NULL |     10 |
+-------+--------+-----------+------+------------+---------+------+--------+
8 rows in set (0.01 sec)
mysql> select * from emp where job in (select distinct job from emp where deptno = 10) and deptno != 10;
+-------+-------+---------+------+------------+---------+------+--------+
| empno | ename | job     | mgr  | hiredate   | sal     | comm | deptno |
+-------+-------+---------+------+------------+---------+------+--------+
|  7369 | SMITH | CLERK   | 7902 | 1980-12-17 |  800.00 | NULL |     20 |
|  7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL |     20 |
|  7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL |     30 |
|  7876 | ADAMS | CLERK   | 7788 | 1987-05-23 | 1100.00 | NULL |     20 |
|  7900 | JAMES | CLERK   | 7698 | 1981-12-03 |  950.00 | NULL |     30 |
+-------+-------+---------+------+------------+---------+------+--------+
5 rows in set (0.00 sec)
mysql> select ename , job , sal , dname from (select ename , job , sal , deptno  from emp where job in (select distinct job from emp where deptno = 10) and deptno != 10) as tmp , deept where tmp.deptno = dept.deptno;
+-------+---------+---------+----------+
| ename | job     | sal     | dname    |
+-------+---------+---------+----------+
| SMITH | CLERK   |  800.00 | RESEARCH |
| JONES | MANAGER | 2975.00 | RESEARCH |
| BLAKE | MANAGER | 2850.00 | SALES    |
| ADAMS | CLERK   | 1100.00 | RESEARCH |
| JAMES | CLERK   |  950.00 | SALES    |
+-------+---------+---------+----------+
5 rows in set (0.00 sec)

所以查询出来的结果也是可以做笛卡尔积的,并且可以作为查询的数据表

3.2 多行子查询

  • 显示工资比部门30的所有员工的工资高的员工的姓名,工资与部门号
  1. 首先找到部门30所有员工的最高工资
  2. 根据tmp的结果作为筛选条件去查询员工信息
mysql> select sal from emp where deptno = 30;
+---------+
| sal     |
+---------+
| 1600.00 |
| 1250.00 |
| 1250.00 |
| 2850.00 |
| 1500.00 |
|  950.00 |
+---------+
6 rows in set (0.00 sec)
mysql> select ename , sal , deptno from emp where sal > (select max(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 |
+-------+---------+--------+
4 rows in set (0.00 sec)
  • 显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门的员工)
  1. 首先找到部门30所有员工的最低工资
  2. 根据tmp的结果作为筛选条件去查询员工信息
mysql> select ename , sal , deptno from emp where sal > (select min(sal) from emp where deptno = 30);
+--------+---------+--------+
| 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 |
+--------+---------+--------+
12 rows in set (0.00 sec)

总结一下,子查询的结果是可以作为查询范围的。并且可以使用以下关键字进行优化:

all关键字:显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号

select ename, sal, deptno from EMP where sal > all(select sal from EMP wheredeptno=30);

any关键字:显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门的员工)

select ename, sal, deptno from EMP where sal > any(select sal from EMP wheredeptno=30);

3.3 多列子查询

单行子查询是指子查询只返回单列,单行数据;多行子查询是指返回单列多行数据,都是针对单列而言的,而多列子查询则是指查询返回多个列数据的子查询语句

来看这个场景:

  • 查询和SMITH的部门和岗位完全相同的所有雇员,不含SMITH本人
  1. 首先查询SMITH的部门与岗位
  2. 将查询出来的部门岗位作为筛选条件,去除SMITH本人
mysql> select deptno , job from emp where ename = 'SMITH';
+--------+-------+
| deptno | job   |
+--------+-------+
|     20 | CLERK |
+--------+-------+
1 row in set (0.00 sec)
mysql> select * from emp where (deptno , job) = (select deptno , job from emp where ename = 'SMITH');
+-------+-------+-------+------+------------+---------+------+--------+
| empno | ename | job   | mgr  | hiredate   | sal     | comm | deptno |
+-------+-------+-------+------+------------+---------+------+--------+
|  7369 | SMITH | CLERK | 7902 | 1980-12-17 |  800.00 | NULL |     20 |
|  7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL |     20 |
+-------+-------+-------+------+------------+---------+------+--------+
2 rows in set (0.00 sec)
mysql> select * from emp where (deptno , job) = (select deptno , job from emp where ename = 'SMITH') and ename != 'SMITH';
+-------+-------+-------+------+------------+---------+------+--------+
| empno | ename | job   | mgr  | hiredate   | sal     | comm | deptno |
+-------+-------+-------+------+------------+---------+------+--------+
|  7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL |     20 |
+-------+-------+-------+------+------------+---------+------+--------+
1 row in set (0.00 sec)

3.4 from中使用子查询

上面我们说过子查询的结果本质也是一种表,也可以进行笛卡尔积!

来看下面几个场景:

  • 显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资
  1. 首先先计算出部门平均工资作为临时表
  2. 然后 临时表与员工表做笛卡尔积筛选出来平均工资高于部门平均工资的员工
  3. 最后与部门表进行笛卡尔积,找到对应的部门名称
mysql> select deptno , AVG(sal) from emp group by deptno;
+--------+-------------+
| deptno | AVG(sal)    |
+--------+-------------+
|     10 | 2916.666667 |
|     20 | 2175.000000 |
|     30 | 1566.666667 |
+--------+-------------+
3 rows in set (0.00 sec)
mysql> select ename , t1.deptno , sal , t1.avg_sal from (select deptno , AVG(sal) avg_sal from emp group by deptno) t1, emp t2 where t1.deptno = t2.deptno and t2.sal > t1.avg_sal;
+-------+--------+---------+-------------+
| ename | deptno | sal     | avg_sal     |
+-------+--------+---------+-------------+
| ALLEN |     30 | 1600.00 | 1566.666667 |
| JONES |     20 | 2975.00 | 2175.000000 |
| BLAKE |     30 | 2850.00 | 1566.666667 |
| SCOTT |     20 | 3000.00 | 2175.000000 |
| KING  |     10 | 5000.00 | 2916.666667 |
| FORD  |     20 | 3000.00 | 2175.000000 |
+-------+--------+---------+-------------+
6 rows in set (0.00 sec)
mysql> select ename , dname , sal , avg_sal from (select ename , t1.deptno deptno, sal , avg_sal from (select deptno , AVG(sal) avg_sal from emp group by deptno) t1, emp t2 where t11.deptno = t2.deptno and t2.sal > t1.avg_sal) t1 , dept t2 where t1.deptno = t2.deptno;
+-------+------------+---------+-------------+
| ename | dname      | sal     | avg_sal     |
+-------+------------+---------+-------------+
| KING  | ACCOUNTING | 5000.00 | 2916.666667 |
| JONES | RESEARCH   | 2975.00 | 2175.000000 |
| SCOTT | RESEARCH   | 3000.00 | 2175.000000 |
| FORD  | RESEARCH   | 3000.00 | 2175.000000 |
| ALLEN | SALES      | 1600.00 | 1566.666667 |
| BLAKE | SALES      | 2850.00 | 1566.666667 |
+-------+------------+---------+-------------+
6 rows in set (0.00 sec)

这样就成功筛选出来了!

  • 显示每个部门的信息(部门名,编号,地址)和人员数量
  1. 对EMP表进行人员统计
  2. 将上面的表看作临时表
mysql> select count(*), deptno from emp group by deptno;
+----------+--------+
| count(*) | deptno |
+----------+--------+
|        3 |     10 |
|        5 |     20 |
|        6 |     30 |
+----------+--------+
3 rows in set (0.01 sec)
mysql> select dept.deptno, dname, mycnt, loc from dept,
    -> (select count(*) mycnt, deptno from emp group by deptno) tmp
    -> where dept.deptno=tmp.deptno;
+--------+------------+-------+----------+
| deptno | dname      | mycnt | loc      |
+--------+------------+-------+----------+
|     10 | ACCOUNTING |     3 | NEW YORK |
|     20 | RESEARCH   |     5 | DALLAS   |
|     30 | SALES      |     6 | CHICAGO  |
+--------+------------+-------+----------+
3 rows in set (0.00 sec)

3.5 合并查询

在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all

  • union :该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行
  • union all:该操作符用于取得两个结果集的并集。当使用该操作符时,不会去掉结果集中的重复行

来看一个场景:

  • 将工资大于2500或职位是MANAGER的人找出来
mysql> 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 |
+-------+---------+-----------+

4 内外连接

数据表的连接分为内连接与外连接

4.1 内连接

内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,我们前面学习的查询都是内连接,也是在开发过程中使用的最多的连接查询。

语法:

select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件;

对于前面的场景可以使用内连接进行书写:显示SMITH的名字和部门名称

-- 用前面的写法
select ename, dname from EMP, DEPT where EMP.deptno=DEPT.deptno and ename='SMITH';
-- 用标准的内连接写法
select ename, dname from EMP inner join DEPT on EMP.deptno=DEPT.deptno and ename='SMITH';

4.2 外连接

外连接分为左外连接和右外连接

左外连接:如果联合查询,左侧的表完全显示我们就说是左外连接

select 字段名 from 表名1 left join 表名2 on 连接条件

右外连接:如果联合查询,右侧的表完全显示我们就说是右外连接。

select 字段名 from 表名1 right join 表名2 on 连接条件

我们建立一个场景,就可以了解外连接做了一件什么是事情了:

-- 建两张表
create table stu (id int, name varchar(30)); -- 学生表
insert into stu values(1,'jack'),(2,'tom'),(3,'kity'),(4,'nono');
create table exam (id int, grade int); -- 成绩表
insert into exam values(1, 56),(2,76),(11, 8);
  • 查询所有学生的成绩,如果这个学生没有成绩,也要将学生的个人信息显示出来
mysql> select * from stu left join exam on stu.id = exam.id;
+------+------+------+-------+
| id   | name | id   | grade |
+------+------+------+-------+
|    1 | jack |    1 |    56 |
|    2 | tom  |    2 |    76 |
|    3 | kity | NULL |  NULL |
|    4 | nono | NULL |  NULL |
+------+------+------+-------+
4 rows in set (0.00 sec)

可以看到对应没有成绩的,通过左外连接也查询到了。

  • 查询所有成绩,如果这个成绩没有对应学生,也要将成绩显示出来
mysql> select * from stu right join exam on stu.id = exam.id;
+------+------+------+-------+
| id   | name | id   | grade |
+------+------+------+-------+
|    1 | jack |    1 |    56 |
|    2 | tom  |    2 |    76 |
| NULL | NULL |   11 |     8 |
+------+------+------+-------+
3 rows in set (0.01 sec)

这样就能看到左外连接与右外连接做了一项什么工作了!

到此这篇关于MySQL从零开始了解数据库开发之复合查询的文章就介绍到这了,更多相关mysql复合查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Windows10下MySQL5.7.19安装教程 MySQL忘记root密码修改方法

    Windows10下MySQL5.7.19安装教程 MySQL忘记root密码修改方法

    这篇文章主要为大家详细介绍了Windows10下MySQL5.7.19安装教程,以及MySQL忘记root密码的修改方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • MySQL数据库INSERT、UPDATE、DELETE以及REPLACE语句的用法详解

    MySQL数据库INSERT、UPDATE、DELETE以及REPLACE语句的用法详解

    本篇文章是对MySQL数据库INSERT、UPDATE、DELETE以及REPLACE语句的用法进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • MySQL三种常用存储引擎InnoDB、MyISAM、Memory深度解析

    MySQL三种常用存储引擎InnoDB、MyISAM、Memory深度解析

    在线教程千万篇,为什么你的 SQL 还是慢?因为没理解存储引擎的底层机制,本文将带你从源码角度彻底搞懂 InnoDB 行锁、聚簇索引,并给出生产环境的最优选型,需要的朋友可以参考下
    2026-05-05
  • IDEA连接mysql又报错!Server returns invalid timezone. Go to tab and set serverTimezone  prope的问题

    IDEA连接mysql又报错!Server returns invalid timezone. Go to tab an

    这篇文章主要介绍了IDEA连接mysql又报错!Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' prope问题,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友可以参考下
    2020-05-05
  • 20分钟MySQL基础入门

    20分钟MySQL基础入门

    这篇文章主要为大家分享了20分钟MySQL基础入门教程,快速掌握MySQL基础知识,真正了解MySQL,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • 浅谈为什么MySQL不建议delete删除数据

    浅谈为什么MySQL不建议delete删除数据

    这篇文章主要介绍了浅谈为什么MySQL不建议delete删除数据,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • MySQL与PHP的基础与应用专题之创建数据库表

    MySQL与PHP的基础与应用专题之创建数据库表

    MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,本系列将带你掌握php与mysql的基础应用,本篇从数据库的创建开始
    2022-02-02
  • mysql中ALTER COLLATION使用场景

    mysql中ALTER COLLATION使用场景

    ALTER COLLATION是SQL中用于修改字符集排序规则的操作,本文主要介绍了mysql中ALTER COLLATION使用场景,具有一定的参考价值,感兴趣的可以了解一下
    2025-05-05
  • 修改MYSQL最大连接数的3种方法分享

    修改MYSQL最大连接数的3种方法分享

    MYSQL数据库安装完成后,默认最大连接数是100,一般流量稍微大一点的论坛或网站这个连接数是远远不够的,增加默认MYSQL连接数的方法有好几个,这里简单分享下
    2011-05-05
  • MySQL常见故障与优化方式

    MySQL常见故障与优化方式

    这篇文章主要介绍了MySQL常见故障与优化方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教<BR>
    2024-04-04

最新评论