目录
1. 基本查询回顾
查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的J
以下是两种有效的写法:
SELECT * FROM emp WHERE (sal > 500 OR job = 'MANAGER') AND ename LIKE 'J%';
SELECT * FROM emp WHERE (sal > 500 OR job = 'MANAGER') AND 'J' = SUBSTRING(ename, 1, 1);
按照部门号升序而雇员的工资降序排序
SELECT * FROM emp ORDER BY deptno ASC, sal DESC;
使用年薪进行降序排序
由于奖金列可能为NULL,我们使用 IFNULL
函数来处理这种情况:
SELECT ename, sal * 12 + IFNULL(comm, 0) AS 年薪 FROM emp ORDER BY 年薪 DESC;
显示工资最高的员工的名字和工作岗位
最高工资我们可以使用聚合函数做统计,但是聚合函数只是对一列的相同数据做聚合,今天还要有名字,所以单纯的做聚合是无法满足需求的,它只能最高工资是多少。名字和工作岗位显示不出来。
因此我们先找最高工资是多少。然后拿着这个最高工资去把这个人和工作岗位找到。很显然这是两句select。
但是这样写太挫了。怎么办呢,想办法把两个select查询语句合在一块写。
sql允许在一条sql内部在执行select查询,这称为子查询,先执行内部的sql然后在执行外部的sql
SELECT ename, job, sal FROM emp WHERE sal = (SELECT MAX(sal) FROM emp);
显示工资高于平均工资的员工信息
使用子查询来找出平均工资,然后筛选出高于平均工资的员工:
SELECT * FROM emp WHERE sal > (SELECT AVG(sal) FROM emp);
显示每个部门的平均工资和最高工资
对部门进行分组,然后找出每个部门的平均工资和最高工资:
SELECT deptno, MAX(sal) AS 最高, AVG(sal) AS 平均 FROM emp GROUP BY deptno;
显示平均工资低于2000的部门号和平均工资
对部门号分组,并筛选出平均工资低于2000的部门(having):
SELECT deptno, AVG(sal) AS myavg FROM emp GROUP BY deptno HAVING myavg < 2000;
显示每种岗位的雇员总数和平均工资
对岗位进行分组,统计每个岗位的人数和平均工资:
SELECT job, COUNT(*), AVG(sal) FROM emp GROUP BY job;
2. 多表查询
在实际开发中,数据通常来自不同的表,需要进行多表查询。
显示雇员名、雇员工资以及所在部门的名字
我们发现上面emp表中是没有部门名称的,换句话说要的数据是从两张表来的。
员工名和员工工资来自于emp表,部门名称来来自于dept表,因此注定了我们要将两个表做整合然后在查询。
select * from emp,dept;
可以看到形成了一张大表,仔细观察一下,将两张表信息做整合的时候,就光SMITH这一条消息就和整个dept表做组合形成了更多的记录,发现下面都是这样的。
新形成表本质是将两张表中数据进行穷举组合的结果。我们把它称之为笛卡尔积。
- 在我们看来这不就是把两张表变成了一张表吗。
- 所以未来在做数据的查找的时候,不就还是相当于单表的查找吗!
- 然后就可以按照条件筛选出想要的信息。
注意: 穷举是把所有组合结果都放在一起了,但是有些信息是有无意义的,因此可以先去除无意义的信息(不过还是看具体情况在决定是否保留),然后在按条件查找。
去除无效信息后筛选
SELECT ename, sal, emp.deptno, dept.dname FROM emp, dept WHERE emp.deptno = dept.deptno;
在我们看来mysql一切皆表,换句话说这里做笛卡尔积之后,它形成的组合结果也是表结构,然后按照条件筛选
- 显示各个员工的姓名、工资及其工资级别
emp表中有员工的姓名和工资,工资级别在工资表里的,因此也还要将两个表笛卡尔积。
SELECT ename, sal, grade FROM emp t1, salgrade t2 WHERE t1.sal BETWEEN t2.losal AND t2.hisal;
上面我们是将两个不同的表做笛卡尔积,那可不可以把同一张表做笛卡尔积呢?
3. 自连接
自连接是指同一张表进行笛卡尔积。
我们发现直接把同一张表做笛卡尔积是不行的。主要原因这是同一张表这样不太好,字段名有重复不知道用的是那个表的字段名。
因此我们可以给两个表做重命名。
重命名也可以对表进行重命名,一旦对表进行重命名之后几乎可以在这条sql语句任何地方出现。因为sql语句执行一定是先告诉是从那个表拿数据。
我们看到同一个表也是拿着前面的表每一条记录去和后面的表中所有记录做组合。所以哪怕是同一张表也可以做笛卡尔积,只不过是对表名重新命名一下即可。
- 显示员工FORD的上级领导的编号和姓名
通过子查询找到FORD的领导编号,然后根据这个编号找到领导信息:
SELECT empno, ename FROM emp WHERE empno = (SELECT mgr FROM emp WHERE ename = 'FORD');
或者使用自连接:
SELECT t2.empno, t2.ename FROM emp t1, emp t2 WHERE t1.mgr = t2.empno AND t1.ename = 'FORD';
4. 子查询
之前编写的时候,子查询我们也写了一些。现在我们正式来说一下子查询的概念。除了刚才的笛卡尔积是一种整合表的做法,子查询也是多表查询或者一张表中复杂查询时常用的做法。
子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询
一般我们在子查询时依赖的永远都是子查询查出来的结果,根据结果我们可以把子查询划分为
- 单列单行子查询
- 单列多行子查询
- 多列单行子查询
- 多列多行子查询
单列子查询
- 单行子查询
显示SMITH同一部门的员工:
SELECT * FROM emp WHERE deptno = (SELECT deptno FROM emp WHERE ename = 'SMITH');
多行子查询
- in关键字 表示相同,判断一个列值是否在集合中。
- all关键字 表示全部
- any关键字 表示任意
- 查询和10号部门的工作岗位相同的雇员的名字、岗位、工资、部门号,但不包含10号部门:
还想要名字怎么办呢,但是emp里面并没有部门名字,只有dept表里面有。结合刚才所学,我们可以进行多表查询。首先确定一定要用的是dept表,还有一张表用谁呢?刚才我们不是已经得到一张表了。所以就把dept表和刚才的表做笛卡尔积。
SELECT ename, job, sal, deptno FROM emp WHERE job IN (SELECT job FROM emp WHERE deptno = 10) AND deptno <> 10;
我们不仅仅用子查询把要的结果筛选出来,我想说的是,一个SQL整体的查询结果本身就是表结构,mysql一切皆表,所以不要认为只有物理上真实存在的表才可以做笛卡尔积,我们可以将一个查出来的表结构也可以和其他表或者其他查询结果做笛卡尔积。
其次,子查询不仅能出现在where后面充当判断条件,而且也能出现在from后面充当笛卡尔积。
在from哪里解释。
- 显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号
先查找30部分最高工资然后充当筛选条件,在筛选
select ename,sal,deptno from emp where sal>(select max(sal) from emp where deptno=30);
除了使用 max 之外,我们还有一种做法,先把30号部门工资筛选出来,然后使用
all关键字 表示全部
select ename,sal,deptno from emp where sal>all(select distinct sal from emp where deptno=30);
- 显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门的员工)
只要比30号部分任意一个人工资高就可以了
any关键字 表示任意
select ename,sal,deptno from emp where sal>any(select distinct sal from emp where deptno=30);
同理也可以使用 min
多列子查询
单行子查询是指子查询只返回单列,单行数据;
多行子查询是指返回单列多行数据,都是针对单列而言的,而多列子查询则是指查询返回多个列数据的子查询语句。
查询和SMITH的部门和岗位完全相同的所有雇员,但不包含SMITH本人:
SELECT * FROM emp WHERE (deptno, job) = (SELECT deptno, job FROM emp WHERE ename = 'SMITH') AND ename <> 'SMITH';
5. 在FROM子句中使用子查询
子查询不仅可以出现where中充当判断条件,也可以出现在from中,from是在sql中告诉数据库去那个表里拿数据。
在这里说一下任意查出来的表结构在我看来全都是表结构。
子查询语句出现在from子句中,把一个子查询结果当做一个临时表使用,可以解决很多问题。
显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资
首先统计每个部门的平均工资:
SELECT deptno, AVG(sal) AS myavg FROM emp GROUP BY deptno;
然后与员工表进行笛卡尔积,并筛选出符合条件的记录:
SELECT ename, emp.deptno, sal, myavg FROM emp, (SELECT deptno, AVG(sal) AS myavg FROM emp GROUP BY deptno) tmp WHERE emp.deptno = tmp.deptno AND sal > myavg;
子查询做表必须要给一个别名
只要你想做还可以在笛卡尔积。所以我们面对非常复杂的查询本质上都是在任务分解,复杂问题是由简单问题构成的。
- 查找每个部门工资最高的人的姓名、工资、部门、最高工资
- 首先也是要分组聚合统计找每个部门的最高工资,只不过只能统计到部门号和部门工资,这个人其他信息是没有办法在group by找到的。
- 然后我们把这个临时表结构和emp做笛卡尔积。
- 最后在筛选出来部门号相同的,这个时候不有我们想要的信息的一张表了吗,然后在筛选自己想要的信息。
然后筛选出部门号相同的信息,最后找出自己要的数据就可以了
SELECT ename, sal, emp.deptno, mymax FROM emp, (SELECT deptno, MAX(sal) AS mymax FROM emp GROUP BY deptno) tmp WHERE emp.deptno = tmp.deptno AND sal = mymax;
记住mysql一切皆表,所谓的一切皆表就意味着可以把查询出来的临时结果在from后面也充当表。
总结一下:
- mysql在我的心里是没有多表结构的,永远就是一张表。
- group by在我看来也是一张表,分组就是分表。
- 只要解决一个问题其他都是解决。
- 多张表我可以在where中充当判断条件,在from中也做一个 临时表 表然后和其他表做笛卡尔积。
- 所以根本就没有多表问题。
⭕ 解决多表问题的本质:想办法将多表转化成为单表,所以mysql中,所有select的问题全部都可以转成单表问题!这就是我们多表查询的指导思想!
6. 合并查询
- 在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all
- 合并并不是笛卡尔积,笛卡尔积是将两个表的信息穷举。合并就是单纯的合起来。
- union把两条sql合并起来并且去掉重复的
- 不想去重使用union all,就会把所有信息保留
union
该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。
注意合并时,两个表结构列必须是一样的才能把两个表合并起来
7. 表的内连接和外连接
表的连接分为内连接和外连接。
7.1 内连接
内连接实际上是利用 WHERE
子句对两个表形成的笛卡尔积进行筛选。我们前面学习的查询都是内连接,这也是开发过程中最常用的连接查询。
除了使用 FROM
逗号连接两个表然后用 WHERE
筛选有效信息,还可以使用 INNER JOIN
连接两个表,并用 ON
和 AND(更推荐where)
级联多个筛选条件来对笛卡尔积进行筛选。之前学到的其实就是内连接的一种。
语法:
SELECT 字段 FROM 表1 INNER JOIN 表2 ON 连接条件 AND 其他条件;
示例:
- 显示SMITH的名字和部门名称之前的写法:
SELECT ename, emp.deptno, dname FROM emp, dept WHERE emp.deptno = dept.deptno AND ename = 'SMITH';
标准内连接写法:
SELECT ename, emp.deptno, dname FROM emp INNER JOIN dept ON emp.deptno = dept.deptno WHERE ename = 'SMITH';
- 两种写法都可以得到同样的数据
- 换句话说这种标准写法可以让我们的sql逻辑更清楚 ,哪一个部分是要形成笛卡尔积的,那一部分是进一步做条件筛选的。
- 内连接中后面的条件也可以用and连接,不过还是建议用where,逻辑更清楚。
7.2 外连接
外连接分为左外连接和右外连接。
左外连接
如果多表查询,我们想让左侧的表完全显示不要过任何过滤筛选,如果和右侧的表配不上,让右侧的都为空也可以。必须保持左侧表的全貌。叫做左外连接。
语法:
SELECT 字段名 FROM 表名1 LEFT JOIN 表名2 ON 连接条件
示例:
- 查询所有学生的成绩,如果这个学生没有成绩,也要将学生的个人信息显示出来如果用
ID
做内连接,只有1号和2号学生符合条件,而我们需要保留左侧表结构的完整性,因此使用左外连接:
SELECT * FROM stu LEFT JOIN exam ON stu.id = exam.id;
左侧表完全保留,右侧表按条件拼接,条件满足直接拼上,条件不满足拼 NULL
。
右外连接
右外连接用于让右侧的表完全显示。如果右侧表的记录在左侧表中找不到匹配项,则左侧表的字段显示为 NULL
。必须保持右侧表的全貌。
语法:
SELECT 字段 FROM 表名1 RIGHT JOIN 表名2 ON 连接条件;
示例:
对 stu
表和 exam
表联合查询,把所有的成绩都显示出来,即使这个成绩没有学生与之对应,也要显示出来
SELECT * FROM stu RIGHT JOIN exam ON stu.id = exam.id;
右侧表完全保留,左侧表按条件拼接,条件满足直接拼上,条件不满足拼 NULL
。
列出部门名称和这些部门的员工信息,同时列出没有员工的部门
首先将 dept
表和 emp
表做连接,要求必须把部门全部显示出来,即使没有员工。这要求以 dept
表为主。使用左外连接:
SELECT dept.dname, emp.* FROM dept LEFT JOIN emp ON dept.deptno = emp.deptno ORDER BY dept.deptno ASC;
使用右外连接:
SELECT dept.dname, emp.* FROM emp RIGHT JOIN dept ON dept.deptno = emp.deptno ORDER BY dept.deptno ASC;
两种方法都可以实现目标,只是表的位置不同。