七种JOINS实现
左上图的JOIN是左外连接,右上图的JOIN是右外连接,中间图的JOIN是内连接,左中图的JOIN在左上图的基础上再去掉中间重复的,只需要 A 在 B 中没有的部分(空的部分),右中图的JOIN在右上图的基础上再去掉中间重复的,只需要 B 在 A 中没有的部分,左下角是全外连接
-- 左上 左外连接
SELECT * FROM
emp LEFT JOIN dept
ON emp.deptno=dept.deptno;
-- 右上 右外连接
SELECT * FROM
emp RIGHT JOIN dept
ON emp.deptno=dept.deptno;
-- 左中 在左外连接的基础上
SELECT * FROM
emp LEFT JOIN dept
ON emp.deptno=dept.deptno
-- 去掉满足连接条件的记录→只需要不满足条件的→连接条件不匹配或者为NULL
WHERE emp.deptno IS NULL;
-- 右中 在右外连接的基础上
SELECT * FROM
emp RIGHT JOIN dept
ON emp.deptno=dept.deptno
-- 去掉满足连接条件的记录→只需要不满足条件的→连接条件不匹配或者为NULL
WHERE emp.deptno IS NULL;
全外连接可以使用以上的两张图拼接而成,一共有 3 种方式可以实现
接下来用左上图和右中图来实现全外连接
-- 左上 左外连接
SELECT * FROM
emp LEFT JOIN dept
ON emp.deptno=dept.deptno -- 去掉;
UNION -- 求并集
-- 右中 在右外连接的基础上
SELECT * FROM
emp RIGHT JOIN dept
ON emp.deptno=dept.deptno
-- 去掉满足连接条件的记录→只需要不满足条件的→连接条件不匹配或者为NULL
WHERE emp.deptno IS NULL;
使用右上图和左中图来实现全外连接
-- 右上 右外连接
SELECT * FROM
emp RIGHT JOIN dept
ON emp.deptno=dept.deptno -- 去掉;
UNION -- 求并集
-- 左中 在左外连接的基础上
SELECT * FROM
emp LEFT JOIN dept
ON emp.deptno=dept.deptno
-- 去掉满足连接条件的记录→只需要不满足条件的→连接条件不匹配或者为NULL
WHERE emp.deptno IS NULL;
右下图可以在全连接的基础上去掉中间部分,或者用左中图和右中图拼接
-- 右下图用左中图和右中图拼接
-- 左中 在左外连接的基础上
SELECT * FROM
emp LEFT JOIN dept
ON emp.deptno=dept.deptno
-- 去掉满足连接条件的记录→只需要不满足条件的→连接条件不匹配或者为NULL
WHERE emp.deptno IS NULL -- 去掉;
UNION
-- 右中 在右外连接的基础上
SELECT * FROM
emp RIGHT JOIN dept
ON emp.deptno=dept.deptno
-- 去掉满足连接条件的记录→只需要不满足条件的→连接条件不匹配或者为NULL
WHERE emp.deptno IS NULL;
补充
USING 指定条件,等值条件的两个字段都是一模一样的,既然是一模一样的,能不能只写一个字段呢?答案是肯定的,USING 可以让 MySQL 自己在表中找这个字段给我们建立关联
-- 内连接
-- 隐式内连接
SELECT *
FROM emp,dept
WHERE emp.deptno=dept.deptno;
-- 显式内连接 语法不同
SELECT *
FROM emp INNER JOIN dept
ON emp.deptno=dept.deptno;
-- 使用USING来关联相同的字段
SELECT *
FROM emp JOIN dept
USING(deptno);
自连接:一张表做等值查询,把一张表作为两张表做等值查询,由于字段不一样,所以不能使用 USING
每一个员工都有领导编号 mgr,领导的信息也在这张表当中
-- 自连接 由于两张表名一样 只能通过取别名区分
SELECT *
FROM emp e, emp m
-- 对应的领导信息
WHERE e.mgr=m.empno;
-- 自连接 由于两张表名一样 只能通过取别名区分
SELECT e.empno,e.ename,m.ename 领导
FROM emp e,emp m
-- 对应的领导信息
WHERE e.mgr=m.empno;
集合运算
MySQL只支持并集运算。
并集即两个集合所有部分
UNION DISTINCT
-
会删除重复行
-
相同的行在结果中只出现一次
A={A,B,C},B={C,D,E}
UNION DISTINCT C={A,B,C,D,E},有两个 C,重复的 C 只保留一个
SELECT * FROM emp
UNION
SELECT * FROM emp WHERE deptno=10;
整个表有 15 条记录
部门编号为 10 有 3 条记录
求并集有 15 条记录,UNION 后面不加默认为 DISTINCT
UNION ALL
-
不会删除重复行
-
相同的行在结果中可能出现多次
A={A,B,C},B={C,D,E}
UNION ALL C={A,B,C,C,D,E},会保留重复的
SELECT * FROM emp
UNION ALL
SELECT * FROM emp WHERE deptno=10;
求并集,保留重复的,共 15 + 3 = 18 条
要求
(1)输入的查询不能包含ORDER BY字句,可以为整个集合运算结果选择性地增加一个ORDER BY字句
SELECT * FROM emp
UNION ALL
SELECT * FROM emp WHERE deptno=10
-- 按姓名查询 对整个临时表排序而不是对emp表排序
ORDER BY ename;
错误用法
SELECT * FROM emp ORDER BY emp.job
UNION ALL
SELECT * FROM emp WHERE deptno=10;
(2)两个查询必须包含相同的列数
SELECT emp.empno FROM emp
UNION ALL
SELECT * FROM emp WHERE deptno=10;
第一个表只有 1 列
第二个表的列数正常
(3)相应列必须具有兼容的数据类型。兼容个的数据类型:优先级较低的数据类型必须能隐式地转换为较高级的数据类型。比如输入的查询1的第一列为int类型,输入的查询2的第一列为float类型,则较低的数据类型int类型可以隐式地转换为较高级float类型。如果输入的查询1的第一列为char类型,输入的查询2的第一列为datetime类型,则会提示转换失败:从字符串转换日期和/或时间时,转换失败
(4)集合运算结果中列名由输入的查询1决定,如果要为结果分配结果列,应该在输入的查询1中分配相应的别名
SELECT emp.empno,emp.ename FROM emp
UNION ALL
SELECT emp.empno 'id',emp.ename 'name' FROM emp WHERE deptno=10;
第一种情况在输入的查询2取别名,结果没有改变
SELECT emp.empno 'id',emp.ename 'name' FROM emp
UNION ALL
SELECT emp.empno,emp.ename FROM emp WHERE deptno=10;
第二种情况在输入的查询1取别名,结果改变
(5)集合运算时,对行进行比较时,集合运算认为两个NULL相等
多表查询练习
-
查询出雇佣日期在1981年的所有员工的编号、姓名、雇佣日期、工作、领导姓名、雇佣月工资、雇佣年工资(基本工资+奖金),工资等级、部门编号、部门名称、部门位置,并且求这些员工的月基本工资在1500~3500之间,将最后的结果按照年工资的降序排列,如果年工资相等,则按照工作进行排序。
需求很多,逐步分析
-
确定所需要的数据表
-
emp:编号、姓名、雇佣日期,工作、月工资、年薪
-
emp:领导姓名
-
dept:部门编号、名称、位置
-
salgrade:工资等级
-
-
确定一致的关联字段
-
员工和领导:e.mgr=e1.empno
-
员工和部门:e.deptno=dept.deptno
-
员工和工资等级:e.sal BETWEEN s.losal AND s.hisal
-
步骤一:查询出所有在1981年雇佣的雇员编号、姓名、雇佣日期、工作、月工资、年工资,并且月薪在1500~3500之间。只需要emp单张表即可
奖金有空的部分,年薪最后的结果为空,需要判断如果奖金为空,就把奖金变成0
-- 获取1981年入职员工的信息并且满足薪资在1500~3500之间
SELECT e.empno,e.ename,e.hiredate,e.sal,(e.sal+IFNULL(e.comm,0))*12 年薪
FROM emp e
WHERE e.sal BETWEEN 1500 AND 3500;
步骤二:加入领导信息,使用自身关联
-- 获取1981年入职员工的信息并且满足薪资在1500~3500之间
SELECT e.empno,e.ename,e.hiredate,e.sal,(e.sal+IFNULL(e.comm,0))*12 年薪,
m.ename 领导
FROM emp e,emp m
WHERE e.sal BETWEEN 1500 AND 3500
-- 获取领导姓名
AND e.mgr=m.empno;
步骤三:加入部门信息
-- 获取1981年入职员工的信息并且满足薪资在1500~3500之间
SELECT e.empno,e.ename,e.hiredate,e.sal,(e.sal+IFNULL(e.comm,0))*12 年薪,
m.ename 领导,d.deptno,d.dname,d.loc
FROM emp e,emp m,dept d
WHERE e.sal BETWEEN 1500 AND 3500
-- 获取领导姓名
AND e.mgr=m.empno
-- 加入部门信息
AND e.deptno=d.deptno;
步骤四:加入工资等级
-- 获取1981年入职员工的信息并且满足薪资在1500~3500之间
SELECT e.empno,e.ename,e.hiredate,e.sal,(e.sal+IFNULL(e.comm,0))*12 年薪,
m.ename 领导,d.deptno,d.dname,d.loc,s.grade 工资等级
FROM emp e,emp m,dept d,salgrade s
WHERE e.sal BETWEEN 1500 AND 3500
-- 获取领导姓名
AND e.mgr=m.empno
-- 加入部门信息
AND e.deptno=d.deptno
-- 加入工资等级
AND e.sal BETWEEN s.losal AND s.hisal;
步骤五:排序
最后的结果按照年工资的降序排列,如果年工资相等,则按照工作进行排序。
-- 获取1981年入职员工的信息并且满足薪资在1500~3500之间
SELECT e.empno,e.ename,e.hiredate,e.job,e.sal,(e.sal+IFNULL(e.comm,0))*12 年薪,
m.ename 领导,d.deptno,d.dname,d.loc,s.grade 工资等级
FROM emp e,emp m,dept d,salgrade s
WHERE e.sal BETWEEN 1500 AND 3500
-- 获取领导姓名
AND e.mgr=m.empno
-- 加入部门信息
AND e.deptno=d.deptno
-- 加入工资等级
AND e.sal BETWEEN s.losal AND s.hisal
-- 排序
ORDER BY 年薪 DESC,e.job ASC;