目录
1. 一个案例引发的多表连接
1.1 案例说明
1.2 笛卡尔积(或交叉连接)的理解
1.3 案例分析与问题解决
2. 多表查询分类讲解
分类1:等值连接 vs 非等值连接
等值连接
非等值连接
编辑
分类2:自连接 vs 非自连接
分类3:内连接 vs 外连接
3. SQL99语法实现多表查询
3.2 内连接(INNER JOIN)的实现
3.3 外连接(OUTER JOIN)的实现
3.3.1 左外连接(LEFT OUTER JOIN)
3.3.2 右外连接(RIGHT OUTER JOIN)
3.3.3 满外连接(FULL OUTER JOIN)
4. UNION的使用
5. 7种SQL JOINS的实现
5.7.1 代码实现
5.7.2 语法格式小结
6. SQL99语法新特性
6.1 自然连接
6.2 USING连接
7. 章节小结
编辑编辑8. 课后练习题
8.1 第一题 根据员工和部门表
1.显示所有员工的姓名,部门号和部门名称
2.查询90号部门员工的job_id和90号部门的location_id
3.选择所有有奖金的员工的 last_name , department_name , location_id , city
4.选择city在Toronto工作的员工的 last_name , job_id , department_id , department_name
5.查询员工所在的部门名称、部门地址、姓名、工作、工资,其中员工所在部门的部门名称为’Executive’
6.选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式
7.查询哪些部门没有员工
8.查询哪个城市没有部门
9.查询部门名为 Sales 或 IT 的员工信息
8.2 第二题 根据生成的新数据库
1. 所有有门派的人员信息 (A、B两表共有)
2. 列出所有用户,并显示其机构信息 (A的全集)
3. 列出所有门派 (B的全集)
4. 所有不入门派的人员 (A的独有)
5. 所有没人入的门派 (B的独有)
6. 列出所有人员和机构的对照关系 (AB全有)
7. 列出所有没入派的人员和没人入的门派 (A的独有+B的独有)
多表查询,也称为关联查询,指两个或更多个表一起完成查询操作。
1. 一个案例引发的多表连接
1.1 案例说明
从多个表中获取数据
# 案例:查询员工的姓名及其部门名称SELECT last_name, department_nameFROM employees, departments;
分析错误情况:
SELECT COUNT (employee_id) FROM employees;# 输出 107 行SELECT COUNT (department_id) FROM departments;# 输出 27 行SELECT 107 * 27 FROM dual ;
我们把上述多表查询中出现的问题称为:笛卡尔积的错误
1.2 笛卡尔积(或交叉连接)的理解
# 查询员工姓名和所在部门名称SELECT last_name,department_name FROM employees,departments;SELECT last_name,department_name FROM employees CROSS JOIN departments;SELECT last_name,department_name FROM employees INNER JOIN departments;SELECT last_name,department_name FROM employees JOIN departments;
1.3 案例分析与问题解决
省略多个表的连接条件(或关联条件)连接条件(或关联条件)无效所有表中的所有行互相连接
SELECT table1 .column , table2 .columnFROM table1, table2WHERE table1 .column 1 = table2 .column 2 ; # 连接条件
# 案例:查询员工的姓名及其部门名称SELECT last_name, department_nameFROM employees, departmentsWHERE employees .department_id = departments .department_id ;
2. 多表查询分类讲解
分类1:等值连接 vs 非等值连接
等值连接
SELECT employees.employee_id, employees.last_name,
employees .department_id , departments .department_id ,departments .location_idFROM employees, departmentsWHERE employees .department_id = departments .department_id ;
拓展1:多个连接条件与 AND 操作符
拓展2:区分重复的列名
SELECT employees .last_name , departments .department_name ,employees .department_idFROM employees, departmentsWHERE employees .department_id = departments .department_id ;
拓展3:表的别名
SELECT e .employee_id , e .last_name , e .department_id ,d .department_id , d .location_idFROM employees e , departments dWHERE e .department_id = d .department_id ;
阿里开发规范 :【 强制 】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。说明 :对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。正例 : select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id;反例 :在某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在 某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出 1052 异常: Column 'name' in field list is ambiguous 。
拓展4:连接多个表
非等值连接
SELECT e .last_name , e .salary , j .grade_levelFROM employees e, job_grades jWHERE e .salary BETWEEN j .lowest_sal AND j .highest_sal ;
分类2:自连接 vs 非自连接
SELECT CONCAT(worker .last_name , ' works for ', manager .last_name )FROM employees worker, employees managerWHERE worker .manager_id = manager .employee_id ;
练习:查询出last_name为 ‘Chen’ 的员工的 manager 的信息。
分类3:内连接 vs 外连接
# 左外连接SELECT last_name,department_nameFROM employees ,departmentsWHERE employees .department_id = departments .department_id (+);# 右外连接SELECT last_name,department_nameFROM employees ,departmentsWHERE employees .department_id (+) = departments .department_id ;
3. SQL99语法实现多表查询
SELECT table1 .column , table2 .column ,table3 .columnFROM table1JOIN table2 ON table1 和 table2 的连接条件JOIN table3 ON table2 和 table3 的连接条件
for t1 in table1:for t2 in table2:if condition1:for t3 in table3:if condition2:output t1 + t2 + t3
可以使用 ON 子句指定额外的连接条件 。这个连接条件是与其它条件分开的。ON 子句使语句具有更高的易读性 。关键字 JOIN 、 INNER JOIN 、 CROSS JOIN 的含义是一样的,都表示内连接
3.2 内连接(INNER JOIN)的实现
SELECT 字段列表FROM A 表 INNER JOIN B 表ON 关联条件WHERE 等其他子句 ;
SELECT e .employee_id , e .last_name , e .department_id ,d .department_id , d .location_idFROM employees e JOIN departments dON (e .department_id = d .department_id );
SELECT employee_id, city, department_nameFROM employees eJOIN departments dON d .department_id = e .department_idJOIN locations lON d .location_id = l .location_id ;
3.3 外连接(OUTER JOIN)的实现
3.3.1 左外连接(LEFT OUTER JOIN)
# 实现查询结果是 ASELECT 字段列表FROM A 表 LEFT JOIN B 表ON 关联条件WHERE 等其他子句 ;
举例
SELECT e .last_name , e .department_id , d .department_nameFROM employees eLEFT OUTER JOIN departments dON (e .department_id = d .department_id ) ;
3.3.2 右外连接(RIGHT OUTER JOIN)
# 实现查询结果是 BSELECT 字段列表FROM A 表 RIGHT JOIN B 表ON 关联条件WHERE 等其他子句 ;
举例:
SELECT e .last_name , e .department_id , d .department_nameFROM employees eRIGHT OUTER JOIN departments dON (e .department_id = d .department_id ) ;
3.3.3 满外连接(FULL OUTER JOIN)
4. UNION的使用
SELECT column ,... FROM table1UNION [ ALL ]SELECT column ,... FROM table2
UNION操作符
举例:查询部门编号>90或邮箱包含a的员工信息
# 方式 1SELECT * FROM employees WHERE email LIKE '%a%' OR department_id> 90 ;
举例:查询中国用户中男性的信息以及美国用户中年男性的用户信息
SELECT id,cname FROM t_chinamale WHERE csex= ' 男 'UNION ALLSELECT id,tname FROM t_usmale WHERE tGender= 'male' ;
5. 7种SQL JOINS的实现
5.7.1 代码实现
# 中图:内连接 A ∩ BSELECT employee_id,last_name,department_nameFROM employees e JOIN departments dON e. `department_id` = d. `department_id` ;
# 左上图:左外连接SELECT employee_id,last_name,department_nameFROM employees e LEFT JOIN departments dON e. `department_id` = d. `department_id` ;
# 右上图:右外连接SELECT employee_id,last_name,department_nameFROM employees e RIGHT JOIN departments dON e. `department_id` = d. `department_id` ;
# 左中图: A - A ∩ BSELECT employee_id,last_name,department_nameFROM employees e LEFT JOIN departments dON e. `department_id` = d. `department_id`WHERE d. `department_id` IS NULL
# 右中图: B-A ∩ BSELECT employee_id,last_name,department_nameFROM employees e RIGHT JOIN departments dON e. `department_id` = d. `department_id`WHERE e. `department_id` IS NULL
# 左下图:满外连接# 左中图 + 右上图 A ∪ BSELECT employee_id,last_name,department_nameFROM employees e LEFT JOIN departments dON e. `department_id` = d. `department_id`WHERE d. `department_id` IS NULLUNION ALL # 没有去重操作,效率高SELECT employee_id,last_name,department_nameFROM employees e RIGHT JOIN departments dON e. `department_id` = d. `department_id` ;
# 右下图# 左中图 + 右中图 A ∪ B- A ∩ B 或者 (A - A ∩ B) ∪ ( B - A ∩ B )SELECT employee_id,last_name,department_nameFROM employees e LEFT JOIN departments dON e. `department_id` = d. `department_id`WHERE d. `department_id` IS NULLUNION ALLSELECT employee_id,last_name,department_nameFROM employees e RIGHT JOIN departments dON e. `department_id` = d. `department_id`WHERE e. `department_id` IS NULL
5.7.2 语法格式小结
# 实现 A - A ∩ Bselect 字段列表from A 表 left join B 表on 关联条件where 从表关联字段 is null and 等其他子句 ;
# 实现 B - A ∩ Bselect 字段列表from A 表 right join B 表on 关联条件where 从表关联字段 is null and 等其他子句 ;
# 实现查询结果是 A ∪ B# 用左外的 A , union 右外的 Bselect 字段列表from A 表 left join B 表on 关联条件where 等其他子句unionselect 字段列表from A 表 right join B 表on 关联条件where 等其他子句 ;
右下图
#实现A∪B - A∩B 或 (A - A∩B) ∪ (B - A∩B)
# 使用左外的 (A - A ∩ B) union 右外的( B - A ∩ B )select 字段列表from A 表 left join B 表on 关联条件where 从表关联字段 is null and 等其他子句unionselect 字段列表from A 表 right join B 表on 关联条件where 从表关联字段 is null and 等其他子句
6. SQL99语法新特性
6.1 自然连接
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments dON e. `department_id` = d. `department_id`AND e. `manager_id` = d. `manager_id` ;
在 SQL99 中你可以写成
SELECT employee_id,last_name,department_nameFROM employees e NATURAL JOIN departments d;
6.2 USING连接
SELECT employee_id,last_name,department_nameFROM employees e JOIN departments dUSING (department_id);
SELECT employee_id,last_name,department_nameFROM employees e ,departments dWHERE e .department_id = d .department_id ;
7. 章节小结
# 关联条件# 把关联条件写在 where 后面SELECT last_name,department_nameFROM employees,departmentsWHERE employees .department_id = departments .department_id ;# 把关联条件写在 on 后面,只能和 JOIN 一起使用SELECT last_name,department_nameFROM employees INNER JOIN departmentsON employees .department_id = departments .department_id ;SELECT last_name,department_nameFROM employees CROSS JOIN departmentsON employees .department_id = departments .department_id ;SELECT last_name,department_nameFROM employees JOIN departmentsON employees .department_id = departments .department_id ;# 把关联字段写在 using() 中,只能和 JOIN 一起使用# 而且两个表中的关联字段必须名称相同,而且只能表示 =# 查询员工姓名与基本工资SELECT last_name,job_titleFROM employees INNER JOIN jobs USING (job_id);#n 张表关联,需要 n-1 个关联条件# 查询员工姓名,基本工资,部门名称SELECT last_name,job_title,department_name FROM employees,departments,jobsWHERE employees .department_id = departments .department_idAND employees .job_id = jobs .job_id ;SELECT last_name,job_title,department_nameFROM employees INNER JOIN departments INNER JOIN jobsON employees .department_id = departments .department_idAND employees .job_id = jobs .job_id ;
8. 课后练习题
8.1 第一题 根据员工和部门表
1. 显示所有员工的姓名,部门号和部门名称。2. 查询 90 号部门员工的 job_id 和 90 号部门的 location_id3. 选择所有有奖金的员工的 last_name , department_name , location_id , city4. 选择 city 在 Toronto 工作的员工的 last_name , job_id , department_id , department_name5. 查询员工所在的部门名称、部门地址、姓名、工作、工资,其中员工所在部门的部门名称为 ’Executive’6. 选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式employees Emp # manager Mgr#kochhar 101 king 1007. 查询哪些部门没有员工8. 查询哪个城市没有部门9. 查询部门名为 Sales 或 IT 的员工信息
1.显示所有员工的姓名,部门号和部门名称
SELECT e.last_name, e.department_id, d.department_name
FROM employees e
LEFT JOIN departments d on e.`department_id` = d.`department_id`;
2.查询90号部门员工的job_id和90号部门的location_id
SELECT e.job_id, d.location_id
FROM employees e
JOIN departments d
ON e.department_id = d.department_id
WHERE e.department_id = 90;
3.选择所有有奖金的员工的 last_name , department_name , location_id , city
SELECT *
FROM employees
WHERE commission_pct is not null;
# 35条记录 老板没有 一个没有部门的人也没有
# 题目要求所有有奖金员工 故应该采用左外连接两次进行操作
SELECT e.last_name, d.department_name, d.location_id, l.city
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id
LEFT JOIN locations l
ON d.location_id = l.location_id
WHERE e.commission_pct is not null;
4.选择city在Toronto工作的员工的 last_name , job_id , department_id , department_name
# 我们知道就一个员工他没有部门,他也没有city 但保险起见我们最好采用左外连接保证先查到所有员工
SELECT e.last_name, e.job_id, e.department_id, d.department_name
FROM employees e
LEFT JOIN departments d on e.department_id = d.department_id
LEFT JOIN locations l on d.location_id = l.location_id
WHERE l.city = 'Toronto';
5.查询员工所在的部门名称、部门地址、姓名、工作、工资,其中员工所在部门的部门名称为’Executive’
SELECT d.department_name, l.street_address, e.last_name, e.job_id, e.salary
FROM employees e
JOIN departments d
ON e.department_id = d.department_id
JOIN locations l
ON d.location_id = l.location_id
WHERE d.department_name = 'Executive';
# 实际当中 考虑外连接场景,思考到有些部门可能没有员工,上面内连接的查询会把没有员工的部门筛除
SELECT d.department_name, l.street_address, e.last_name, e.job_id, e.salary
FROM departments d
LEFT JOIN employees e
ON e.department_id = d.department_id
LEFT JOIN locations l
ON d.location_id = l.location_id
WHERE d.department_name = 'Executive';
6.选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式
SELECT e.last_name "employees", e.employee_id "Emp#", m.last_name "manager", m.employee_id "Mgr#"
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.employee_id;
7.查询哪些部门没有员工
SELECT department_name
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
WHERE e.department_id is null;
8.查询哪个城市没有部门
SELECT city
FROM locations l
LEFT JOIN departments d ON l.location_id = d.location_id
WHERE d.location_id is null;
9.查询部门名为 Sales 或 IT 的员工信息
SELECT employee_id, last_name, salary
FROM employees e
JOIN departments d on d.department_id = e.department_id
WHERE d.department_name IN ('Sales', 'IT');
8.2 第二题 根据生成的新数据库
建表操作
储备:建表操作:
CREATE TABLE `t_dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `t_emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
empno int not null,
PRIMARY KEY (`id`),
KEY `idx_dept_id` (`deptId`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);
1. 所有有门派的人员信息 (A、B两表共有)
select *
from t_emp a inner join t_dept b
on a.deptId = b.id;
2. 列出所有用户,并显示其机构信息 (A的全集)
select *
from t_emp a left join t_dept b
on a.deptId = b.id;
3. 列出所有门派 (B的全集)
select *
from t_dept b;
4. 所有不入门派的人员 (A的独有)
select *
from t_emp a left join t_dept b
on a.deptId = b.id
where b.id is null;
5. 所有没人入的门派 (B的独有)
select *
from t_dept b left join t_emp a
on a.deptId = b.id
where a.deptId is null;
6. 列出所有人员和机构的对照关系 (AB全有)
#MySQL Full Join的实现 因为MySQL不支持FULL JOIN,下面是替代方法
#left join + union(可去除重复数据)+ right join
SELECT *
FROM t_emp A LEFT JOIN t_dept B
ON A.deptId = B.id
UNION
SELECT *
FROM t_emp A RIGHT JOIN t_dept B
ON A.deptId = B.id
7. 列出所有没入派的人员和没人入的门派 (A的独有+B的独有)
SELECT *
FROM t_emp A LEFT JOIN t_dept B
ON A.deptId = B.id
WHERE B.`id` IS NULL
UNION
SELECT *
FROM t_emp A RIGHT JOIN t_dept B
ON A.deptId = B.id
WHERE A.`deptId` IS NULL;