MySQL 多表查询

总结内容

1. 为什么使用多表查询

需要查询的数据分散在多张表中,只有联合多张表才能查询出期望的数据。比如:emp 表存储了员工的信息,dept 表存储了部门的信息,而需求是查询出员工的编号、姓名以及对应部门的名称,表信息如下:

  1. 员工表 emp
    在这里插入图片描述
  2. 部门表 dept
    在这里插入图片描述

2. 笛卡尔积

  • 数学中:假设集合 A = {a, b},集合 B = {0, 1, 2},则两个集合的笛卡尔积为 {(a, 0), (a, 1), (a, 2), (b,0), (b, 1), (b, 2)}。
  • MySQL 中:多表查询会产生笛卡尔积,比如:SELECT * FROM emp, dept,实际运行环境下,应避免使用全笛卡尔集。

解决方案:在 WHERE 语句中加入有效的连接条件 --> 一般是等值连接,注意:连接 n 张表,至少需要 n-1 个连接条件。

3. 多表查询分类

  • 内连接查询
    隐式内连接查询
    显示内连接查询
  • 外连接查询
    左外连接查询
    右外连接查询
    全外连接查询

4. 内连接查询

4.1 隐式内连接查询

SELECT [DISTINCT] * | 字段 [别名] [, 字段 [别名],] 
FROM 表名称 [别名], [表名称 [别名],] 
[WHERE 条件(S)/消除笛卡尔积连接] 
[ORDER BY 排序字段 [ASC|DESC] [, 排序字段 [ASC|DESC],]];
  • 在 WHERE 子句中写入连接条件;
  • 当多个表中有重名列时,必须在列的名字前加上表名作为前缀或者表的别名(使用别名更简单,性能更高);
  • 等值连接是连接操作中最常见的一种,通常是在存在主外键约束条件的多表上建立的,连接条件中的两个字段通过等号建立等值关系。

4.2 显式内连接查询

  • 语法
SELECT table1.column, table2.column 
FROM table1 [INNER] JOIN table2 ON table1.column1 = table2.column2 WHERE 条件

显示内连接查询:查询的结果和隐式内连接一模一样。区别在于:

  • 显示内连接可以看到 [INNER] JOIN;
  • 消除笛卡尔积条件使用写在 ON 子句。

5. 外连接查询

5.1 需求

查询出员工的编号,名字,薪水和所在部门的名称(使用内连接查询),SQL 如下:

SELECT emp.EMPNO, emp.ENAME, emp.SAL, dept.DNAME FROM emp JOIN dept ON emp.DEPTNO = dept.DEPTNO

执行完会发现,没有部门的员工则查询不出来。

5.2 左外连接查询

SELECT table1.column, table2.column 
FROM table1 LEFT [OUTER] JOIN table2 ON table1.column1 = table2.column2 
WHERE 条件
SELECT emp.EMPNO, emp.ENAME, emp.SAL, dept.DNAME 
FROM emp LEFT JOIN dept ON emp.DEPTNO = dept.DEPTNO

查询出 JOIN 左边表的全部数据查询出来,JOIN 右边的表不匹配的数据使用 NULL 来填充数据。

5.3 右外连接查询

SELECT table1.column, table2.column 
FROM table1 RIGHT [OUTER] JOIN table2 ON table1.column1 = table2.column2 WHERE 条件
SELECT emp.EMPNO, emp.ENAME, emp.SAL, dept.DNAME 
FROM dept RIGHT JOIN emp ON emp.DEPTNO = dept.DEPTNO

查询出 JOIN 右边表的全部数据查询出来,JOIN 左边的表不匹配的数据使用 NULL 来填充数据。

6. 分组函数

6.1 函数分类

  • 单行函数:将每条数据进行独立的计算,然后每条数据得到一条结果;
  • 多行函数:多条数据同时计算,最终得到一条结果数据。也成为聚集函数、分组函数,主要用于完成一些统计功能等等。

6.2 多行函数

  • COUNT():查询表中的数据记录;
  • AVG():求出平均值;
  • SUM():求和;
  • MAX():求出最大值;
  • MIN():求出最小值。
    注意:
  • 统计函数忽略空值,可以使用 IFNULL, 因为是 NULL 不会影响汇总值,但会影响汇总数量;
  • 不能在 where 语句中使用分组函数。

7. 分组查询

7.1 分组语法

在 MySQL 使用 GROUP BY 来实现分组。语法如下:

SELECT [DISTINCT] *|分组字段1 [别名] [, 分组字段2 [别名] ,] | 统计函数 
FROM 表名称 [别名], [表名称 [别名] ,] 
[WHERE 条件(s)] 
[GROUP BY 分组字段1 [, 分组字段2 ,]] 
[ORDER BY 排序字段 ASC | DESC [, 排序字段 ASC | DESC]];

分组效果:

  • 使用 GROUP BY 子句将表分成小组;
  • 结果集隐式按升序排列,如果需要改变排序方式可以使用 ORDER BY 子句。

7.2 使用分组注意

  • SELECT 子句出现的字段,要不在统计函数中,要不出现在 GROUP BY 子句中,否则不合理(整体与个体);
  • 在GROUP BY 子句中出现的字段,可以不出现在 SELECT 列表中;
  • 统计函数可以单独使用,SQL 中可以没有 GROUP BY 子句;
  • 在 GROUP BY 子句中,可以按单列进行分组,也可以在多列上进行分组,多列分组就是按照多个字段的组合进行分组,最终的结果也会按照分组字段进行排序显示。

7.3 分组限定

  • 不能在 WHERE 子句中对分组限定,限制组须使用 HAVING 子句;
  • 不能在 WHERE 子句中使用统计函数,而在 HAVING 子句可使用统计函数。
  • 查询在 80, 81, 82 年各进公司多少人
SELECT YEAR(HIREDATE) y, COUNT(EMPNO) 
FROM emp 
GROUP BY YEAR(HIREDATE) 
HAVING y BETWEEN '1980' AND '1982'
  • 查询各个管理人员下员工的平均工资,其中最低工资不能低于 1300,不计算老板
SELECT MGR, AVG(SAL), MIN(SAL) AS minsal 
FROM emp 
GROUP BY MGR 
HAVING MGR IS NOT NULL AND minsal >= 1300

8. 单行函数

8.1 日期函数

  • NOW():获取当前时间;
  • DAY(date):获取日期中的天数,范围是从 1 到 31;
  • HOUR(time):返回 time 对应的小时数,范围是从 0 到 23;
  • MINUTE(time):返回 time 对应的分钟数,范围是从 0 到 59;
  • MONTH(date):返回 date 对应的月份,范围时从 1 到 12;
  • YEAR(date):返回 date 对应的年份,范围是从 1000 到 9999;
  • LAST_DAY(date):获取一个日期或日期时间值,返回该月最后一天对应的值。

8.2 日期转换为字符串

DATE_FORMAT(date, format):把日期转换为字符串。其中 format 中常见格式化符号如下(更多参见MySQL 5.5 中文参考手册 549 页):
在这里插入图片描述

SELECT DATE_FORMAT(NOW(), '%Y-%m-%d')

9. 子查询

9.1 定义和作用

子查询指的就是在一个查询之中嵌套了其他的若干查询。
在使用 SELECT 语句查询数据时,有时候会遇到这样的情况,在 WHERE 查询条件中的限制条件不是一个确定的值,而是一个来自于另一个查询的结果(比如查询大于平均工资的员工)。

SELECT select_list 
FROM table 
WHERE expr operator (SELECt select_list FROM table)

注意

  • 子查询一般出现在 FROM 和 WHERE 子句中;
  • 子查询要使用圆括号括起来;
  • 将子查询放在比较运算符的右边(增强可读性);
  • 子查询在主查询前执行一次,主查询使用子查询的结果;但不宜嵌套过多。

9.2 分类

按查询结果分类:

  • 单行单列:只包含一个字段的查询,返回的查询结果也只包含一行数据;
  • 多行单列:只包含了一个字段,但返回的查询结果可能多行或者零行;
  • 多行多列:包含多个字段的返回,查询结果可能是单行或者多行,好比是一张表。

9.3 单行单列

一般用于 WHERE 之后的子查询,子查询结果是一行一列记录。
使用单行记录比较运算符:=、>、>=、<、<=、<>。

  • 查询出工资比 MARTIN 还要高的全部雇员信息
SELECT * FROM emp WHERE sal > (SELECT sal FROM emp WHERE ename = 'MARTIN')
  • 查询平均工资高于公司平均工资的部门信息
SELECT e.deptno, d.dname, AVG(sal)
FROM emp e JOIN dept d ON e.deptno = d.deptno 
GROUP BY deptno HAVING AVG(sal) >= (SELECT AVG(sal) FROM emp)

9.4 多行单列

一般也用于 WHERE 子句中,子查询结果只有一列,但是有多行。使用多行比较运算符:

  • IN :与列表中的任意一个值相等
  • ANY :与子查询返回的任意一个值比较
  • =ANY :此时和 IN 操作符相同
  • ANY :大于子查询中最小的数据

  • <ANY :小于子查询中最大的数据
  • ALL :与子查询返回的每一个值比较
  • ALL :大于子查询中最大的数据

  • <ALL :小于子查询中最小的数据

9.5 多行多列

子查询的结果是多行多列,一般会把子查询返回的结果当成一个临时表,接着在临时表上继续查询或者连接查询;
注意:多行多列的子查询返回的结果必须要设置一个临时表名。

  • 查询出每个部门的编号、名称、部门人数、平均工资
select d.deptno, d.dname, count(e.empno), avg(sal) from emp e right join dept d on e.deptno = d.deptno group by e.DEPTNO

10 练习

10.1 练习题和答案

# 查询所有员工信息
SELECT * from emp
# 查询每个员工的编号、姓名、职位
SELECT EMPNO, ename, job from emp
# 查询所有部门信息
select * from dept
# 查询所有有员工的部门编号
select ename,deptno from emp
# 查询有员工的部门和职位
SELECT DISTINCT job,  DEPTNO FROM emp
# 查询所有员工的年薪
SELECT ename, (sal * 12) '年薪' from emp
# 查询所有员工的年薪(使用别名)
SELECT ename '姓名', (sal * 12) '年薪' from emp
# 查询所有员工的年薪((月薪 + 奖金) * 12)
select ename '姓名', (sal + ifnull(comm, 0)) * 12 from emp
# 查询有奖金的员工信息
select * from emp where not comm is null and comm > 0
# 查询公司的老板
select * from emp where mgr is null
# 查询出基本工资高于 1500 的所有员工信息
select * from emp where sal > 1500
# 查询名字叫 SCOTT 的员工所从事的工作
select job from emp where ename = 'scott'
# 查询 1981 年入职的员工信息
select * from emp where year(hiredate) in ('1981')
# 查询年薪小于 3W 的员工
select * from emp where (ifnull(comm, 0) + sal) * 12 < 30000
# 查询所有不是销售人员的员工信息
select * from emp where not job = 'salesman'
# 查询工资在 2000-3000 之间的员工信息
select * from emp where sal between 2000 and 3000
# 查询 1981 年入职的员工
select * from emp where year(hiredate) = '1981'
# 查询工资为 800 或 1600 或 3000 的员工
select * from emp where sal in (800, 1600, 3000)
# 查询出所有雇员姓名是以 A 开头的全部雇员信息。
select * from emp where ename like 'M%'
# 查询出雇员姓名第二个字母是 M 的全部雇员信息。
select * from emp where ename like '_M%'
# 查询出雇员姓名任意位置上包含字母 A 的全部雇员信息。
select * from emp where ename like '%A%'
# 查询姓名中有 e 或者 a 的员工姓名
select * from emp where ename like '%e%' or ename like '%a%'
# 查询工资在 1500~3000 之间的全部员工信息
select * from emp where sal BETWEEN 1500 and 3000
# 查询工资不在 2000-3000 之间的员工信息
select * from emp where not sal between 2000 and 3000
# 查询工资不为 800 或 1600 或 3000 的员工
select * from emp where sal not in(800, 1600, 3000)
# 查询出职位是办事员 (CLERK) 或者是销售人员 (SALESMAN) 的全部信息,且工资在 1000 以上
select * from emp where (job = 'clerk' or job = 'salesman') and sal > 1000
# 查询所有员工信息,按照工资排序
select * from emp order by sal
# 查询所有员工信息,按照年薪降序排序
select * from emp order by (ifnull(comm, 0) + sal) * 12 desc
# 查询所有员工信息,按照部门和年薪降序排序
select * from emp ORDER BY deptno desc, (ifnull(comm, 0) + sal) * 12 desc
# 查询员工编号,员工名称,员工所属部门的编号和名称
select empno, ename, emp.deptno, dname from emp join dept on emp.DEPTNO = dept.DEPTNO 
# 查询员工的姓名,工资,所在部门的名称,以及工资的等级
select ename, sal, dname,loc from emp e, dept d where e.deptno = d.deptno
# 查询所有员工每个月的平均工资及总工资
select sum(ifnull(comm, 0) + sal) / count(*) '平均工资', sum(ifnull(comm, 0) + sal) '总工资' from emp
# 查询月薪在 2000 以上的员工总人数
select count(*) from emp where  (ifnull(comm,0) + sal) > 2000
# 查询员工最高工资和最低工资差距
select max(ifnull(comm,0) + sal) - min(ifnull(comm,0) + sal) '工资差' from emp
# 按照职位分组,求出每个职位的最高和最低工资
select job, max(sal) '最高工资', min(sal) '最低工资' from emp GROUP BY job
# 查询出每一个部门员工的平均奖金
select dname, avg(ifnull(comm,0)) from emp join dept on emp.deptno = dept.deptno group by emp.deptno
# 查询出每一个部门员工的平均工资
select dname, avg(ifnull(comm, 0) + sal) from emp join dept on emp.deptno = dept.deptno GROUP BY emp.deptno
# 查询各个部门和岗位的平均工资
select dname '部门', job '职位', avg(ifnull(comm,0) + sal) from emp e join dept d on e.deptno = d.deptno group BY e.deptno, e.job
select deptno '部门', job '职位', avg(ifnull(comm,0) + sal) from emp GROUP BY deptno, job
# 查询部门平均工资高于 2000 的部门及其平均工资
select dname, avg(ifnull(comm,0) + sal) from emp e join dept d on e.deptno = d.deptno GROUP BY d.deptno having avg(ifnull(comm,0) + sal) > 2000
# 查询在 80, 81, 82 年各进公司多少人
select count(*), YEAR(hiredate) y from emp group by year(hiredate) having y between '1980' and '1982'
# 查询出工资比 MARTIN 还要高的全部雇员信息
select * from emp where sal > (select sal from emp where ename = 'martin')
# 查询各个管理人员下员工的平均工资,其中最低工资不能低于 1300,不计算老板
select mgr, avg(sal), min(sal) minsal from emp group by mgr having mgr is not null and minsal >= 1300
# 查询平均工资高于公司平均工资的部门信息
select e.deptno, d.dname, avg(sal) from emp e join dept d  on e.deptno = d.deptno GROUP BY deptno having avg(sal) >= (select avg(sal) from emp)
# 查询工资等于部门经理(职位是 MANAGER)的员工信息
select * from emp where sal in (select sal from emp where job = 'manager') and job != 'manager'
# 查询出每个部门的编号、名称、部门人数、平均工资
select d.deptno, d.dname, count(e.empno), avg(sal) from emp e right join dept d on e.deptno = d.deptno group by e.DEPTNO
select d.deptno, d.dname, count(e.empno), avg(sal) from dept d, emp e where e.deptno = d.deptno group by e.DEPTNO
# 查询一个员工的名字和对应领导名字
select e.ename, t.ename from emp e, (select * from emp) t where e.mgr = t.empno
select e.ename, t.ename from emp e left join (select * from emp) t on e.mgr = t.empno;
select * from emp e, emp  mgr where e.mgr = mgr.empno
select * from emp e left join emp mgr on e.mgr = mgr.empno

总结

以上就是对 MYSQL的 SQL 语句的总结了,代码仅供参考,欢迎讨论交流。

posted @ 2021-05-13 17:17  Yan_Yang  阅读(151)  评论(0编辑  收藏  举报