记录相关操作之多表查询

一. 介绍

本节主题

  • 多表连接查询
  • 复合条件连接查询
  • 子查询

二. 准备表

# 建表
create table dep(
    id int,
    name varchar(20) 
);

create table emp(
    id int primary key auto_increment,
    name varchar(20),
    sex enum('male','female') not null default 'male',
    age int,
    dep_id int
);

#插入数据
insert into dep values
    (200,'技术'),
    (201,'人力资源'),
    (202,'销售'),
    (203,'运营'),
    (205,'爆破');

insert into emp(name,sex,age,dep_id) values
    ('jason','male',18,200),
    ('egon','female',48,201),
    ('kevin','male',18,201),
    ('nick','male',28,202),
    ('owen','male',18,203),
    ('jerry','female',18,204);
drop table emp;    
drop table dep;

三. 笛卡尔积

select * from dep,emp;  # 结果: 笛卡尔积

select * from emp,dep where emp.dep_id=dep.id;
select emp.id,emp.name,emp.sex,emp.age,dep.name from emp,dep 
    where emp.dep_id=dep.id;

四. 多表连接查询

1. 外连接语法

select 字段列表 
    from 表1 inner|left|right join 表2 
    on 表1.字段 = 表2.字段;

2. 四种外连接介绍

"""
MySQL也知道你在后面查询数据过程中肯定会经常用到拼表操作
所以特地给你开设了对应的方法:
    inner join    外连接之内连接(只拼接2张表中共有的数据部分)
    1eft  join    外连接之左连接(左表所有的数据都展示出来, 没有对应的项就用NULL)
    right join    外连接之右连接(右表所有的数据都展示出来, 没有对应的项就用NULL)
    union         外连接之全连接(全连接左右两 表所有的数据都展示出来)
        注意:mysql不支持全外连接 full join
        强调:mysql可以使用下面的方式间接实现全外连接.
"""
# 内连接
select * from emp inner join dep on emp.dep_id=dep.id;

# 左连接
select * from emp left join dep on emp.dep_id=dep.id;

# 右连接
select * from emp right join dep on emp.dep_id=dep.id;

# 全连接
select * from emp left join dep on emp.dep_id=dep.id
union
select * from emp right join dep on emp.dep_id=dep.id;


select * from emp union dep on emp.dep_id=dep.id;

五. 符合条件连接查询

# 以内连接的方式查询emp和dep表,并且emp表中的age字段值必须大于25,即找出年龄大于25岁的员工以及员工所在的部门
select emp.name,dep.name
    from emp inner join dep on emp.dep_id=dep.id
    where emp.age>25;

以内连接的方式查询emp和dep表,并且以age字段的升序方式显示
select emp.id, emp.name, emp.sex, emp.age, dep.name
    from emp inner join dep on emp.dep_id=dep.id 
    order by age asc;

六. 子查询

1. 介绍

# 1:子查询是将一个查询语句嵌套在另一个查询语句中。
# 2:内层查询语句的查询结果,可以为外层查询语句提供查询条件。
# 3:子查询中可以包含:IN、NOT IN、ANY、ALL、EXISTS 和 NOT EXISTS等关键字
# 4:还可以包含比较运算符:= 、 !=、> 、<等

2. 带in关键字的子查询

"""
子查询就是我们平时解决问题的思路
	分步骤解决问题
		第一步
		第二步
		...
将一个查询语句的结果当做另外一个查询语句的条件去用
"""
# 查询部门是技术或者人力资源的员工信息
'''
1. 先获取部门的id号
2. 再去员工表里面筛选出对应的员工
'''
# 第一步:
select id from dep where name in ('技术', '人力资源');
# 第二步:
select * from emp where dep_id in (200, 201);

# 得出结果:
select * from emp where dep_id in (select id from dep where name in ('技术', '人力资源'));

3. 带比较运算符的子查询

# 比较运算符:=、!=、>、>=、<、<=、<>
# 查询大于所有人平均年龄的员工名与年龄
select avg(age) from emp;
select name,age from emp where age > (select avg(age) from emp);


# 查询大于部门内平均年龄的员工名、年龄
select dep_id,avg(age) from emp group by dep_id;
select emp.name,emp.age 
    from emp inner join (select dep_id,avg(age) as avg_age from emp group by dep_id) as t1 
    on emp.dep_id=t1.dep_id 
    where emp.age > t1.avg_age;

4. 带exists关键字的子查询

"""
exists关字键字表示存在。在使用exists关键字时,内层查询语句不返回查询的记录。
而是返回一个真假值。True或False
当返回True时,外层查询语句将进行查询;当返回值为False时,外层查询语句不进行查询
"""
# dep表中存在dept_id=203,Ture
select * from emp
    where exists (select id from dep where id=203);
    
# dep表中不存在dept_id=204,False   
select * from emp
    where exists (select id from dep where id=204);

5. 练习: 查询每个部门最新入职的那位员工

准备表和记录

company.emp
    员工id      id                  int             
    姓名        emp_name            varchar
    性别        sex                 enum
    年龄        age                 int
    入职日期     hire_date           date
    岗位        post                varchar
    职位描述     post_comment        varchar
    薪水        salary              double
    办公室       office              int
    部门编号     depart_id           int



#创建表
create table emp(
id int not null unique auto_increment,
name varchar(20) not null,
sex enum('male','female') not null default 'male', #大部分是男的
age int(3) unsigned not null default 28,
hire_date date not null,
post varchar(50),
post_comment varchar(100),
salary double(15,2),
office int, #一个部门一个屋子
depart_id int
);


#插入记录
#三个部门:教学,销售,运营
insert into emp(name,sex,age,hire_date,post,salary,office,depart_id) values
('egon','male',18,'20170301','老男孩驻沙河办事处外交大使',7300.33,401,1), #以下是教学部
('alex','male',78,'20150302','teacher',1000000.31,401,1),
('wupeiqi','male',81,'20130305','teacher',8300,401,1),
('yuanhao','male',73,'20140701','teacher',3500,401,1),
('liwenzhou','male',28,'20121101','teacher',2100,401,1),
('jingliyang','female',18,'20110211','teacher',9000,401,1),
('jinxin','male',18,'19000301','teacher',30000,401,1),
('成龙','male',48,'20101111','teacher',10000,401,1),

('歪歪','female',48,'20150311','sale',3000.13,402,2),#以下是销售部门
('丫丫','female',38,'20101101','sale',2000.35,402,2),
('丁丁','female',18,'20110312','sale',1000.37,402,2),
('星星','female',18,'20160513','sale',3000.29,402,2),
('格格','female',28,'20170127','sale',4000.33,402,2),

('张野','male',28,'20160311','operation',10000.13,403,3), #以下是运营部门
('程咬金','male',18,'19970312','operation',20000,403,3),
('程咬银','female',18,'20130311','operation',19000,403,3),
('程咬铜','male',18,'20150411','operation',18000,403,3),
('程咬铁','female',18,'20140512','operation',17000,403,3)
;

# ps:如果在windows系统中,插入中文字符,select的结果为空白,可以将所有字符编码统一设置成gbk

外连接查询

select * from emp order by hire_date;
select post,min(hire_date) as min_hire_date from emp group by post;

select * 
    from emp inner join (select post,min(hire_date) as min_hire_date from emp group by post) t1
    on emp.hire_date=t1.min_hire_date;

子查询(难理解)

# 错误写法
select (select * from emp as t2 
        where t1.post=t2.post 
        order by hire_date desc
        limit 1)
    from emp as t1 group by post;    

答案一为正确答案,答案二中的limit 1有问题(每个部门可能有>1个为同一时间入职的新员工),我只是想用该例子来说明可以在select后使用子查询

可以基于上述方法解决:比如某网站在全国各个市都有站点,每个站点一条数据,想取每个省下最新的那一条市的网站质量信息

5.总结

"""
表的查询结果可以作为其他表的查询条件, 也可以通过起别名的方式把它作为一个张虚拟表根其他表关联.

多表查询就两种方式:
    先拼接表再查询
    子查询 一步一步来
"""

七. 查询语句的优先级

from   
笛卡尔积  # 把两张表简单粗暴的连接到一起
on
left join 或者 right join
inner join
where 
group by
having
distinct
order by
limit

八. 综合练习

1. 查询所有的课程的名称以及对应的任课老师姓名

# 1. 将teacher表与course关联
SELECT
	* 
FROM
	course
	INNER JOIN teacher ON course.teacher_id = teacher.tid;
	
# 2. 从关联的表中筛选出课程,老师字段
SELECT
	course.cname,
	teacher.tname 
FROM
	course
	INNER JOIN teacher ON course.teacher_id = teacher.tid;	

2. 查询平均成绩大于八十分的同学的姓名和平均成绩

# 1. 对score表的student_id对象进行分组拿到学生的student_id,再进行having对平均成绩进行过滤, 保留平均成绩作为输出对象
SELECT
	student_id,
	avg( num ) AS avg_num 
FROM
	score 
GROUP BY
	student_id 
HAVING
	avg( num )> 80;
	
# 2. 步骤1得到的表结构中的student_id字段与student表的sid进行连表操作
SELECT
	student.sname,
	t1.avg_num 
FROM
	student
	INNER JOIN ( SELECT student_id, avg( num ) AS avg_num FROM score GROUP BY score.student_id HAVING avg( num )> 80 ) AS t1 ON student.sid = t1.student_id;

3. 查询没有报李平老师课的学生姓名

# 1. teacher表与course表连表, 再对连表结果中的李平老师的课程过滤出来
SELECT
	course.cid 
FROM
	teacher
	INNER JOIN course ON teacher.tid = course.teacher_id where teacher.tname='李平老师';
	
# 2. 步骤1得到的新表的cid作为score表的条件, 再对student_id进行分组. 作为student表的条件.
# 注意: 在成绩表中. 学生可以报了李平老师的课同时也包了别的老师的课, 如果仅仅是取反, 派出了该学生仅仅之争对与李平老的课的student_Id, 该学生报了别的老师ide课的student_id还存在, 而这两个student_id都是指向的同一个学生, 这样这个学生还是无法排除的. 我们应该先取到报过李平老师的课程的学生, 再去重, 去重以后就是报了李平老师的student_id们, 这样再通过与学生表就可以进行取反筛选.
# SELECT
# 	student_id
# FROM
# 	score
# WHERE
# 	score.course_id NOT IN ( SELECT course.cid FROM course INNER JOIN teacher ON teacher.tid = course.teacher_id WHERE teacher.tname = '李平老师' )
# GROUP BY
# 	student_id;
SELECT DISTINCT
	student_id
FROM
	score
WHERE
	score.course_id IN ( SELECT course.cid FROM course INNER JOIN teacher ON teacher.tid = course.teacher_id WHERE teacher.tname = '李平老师' );

# 3. 步骤2得到的新表的student_id结果作为student的条件.
SELECT
	student.sname
FROM
	student
WHERE
	student.sid NOT IN (
	SELECT DISTINCT
		student_id
	FROM
		score
	WHERE
	score.course_id IN ( SELECT course.cid FROM course INNER JOIN teacher ON teacher.tid = course.teacher_id WHERE teacher.tname = '李平老师' )
	);

4. 查询没有同时选修物理课程和体育课程的学生姓名(只要选了一门的, 选了2们或者没有选的都不要)

# 1. 通过course表拿到对于物理课程和体育课程的cid, 作为score的条件
SELECT
	cid 
FROM
	course 
WHERE
	cname IN ( '物理', '体育' );

# 2. 拿到步骤1的查询结果作为score的where条件,再对student_id进行分组, 再使用having过滤出每个组中学生选修课程数量要等于1
SELECT
	student_id 
FROM
	score 
WHERE
	score.course_id IN (
	SELECT
		cid 
	FROM
		course 
	WHERE
	cname IN ( '物理', '体育' )) 
GROUP BY
	score.student_id 
HAVING
	count( course_id )= 1;
	
# 3. 拿到步骤2的结果作为student的where条件.
SELECT
	student.sname 
FROM
	student 
WHERE
	student.sid IN (
	SELECT
		student_id 
	FROM
		score 
	WHERE
		score.course_id IN (
		SELECT
			cid 
		FROM
			course 
		WHERE
		cname IN ( '物理', '体育' )) 
	GROUP BY
		score.student_id 
	HAVING
	count( course_id )= 1 
	);

5. 查询挂科超过两门(包括两门)的学生姓名和班级

# 1. 对score表的num字段进行where筛选, 再对student_id进行分组, 对分组的结果进行having过滤出count(course_id)>=2的student_id组.
SELECT
	student_id 
FROM
	score 
WHERE
	num < 60 
GROUP BY
	student_id 
HAVING
	count( course_id )>= 2;
	
# 2. 连接class表和student表
SELECT
	class.caption,
	student.sname 
FROM
	student
	INNER JOIN class ON student.class_id = class.cid;
	
# 3. 步骤1作为步骤2的where条件, 输出学生姓名和班级
SELECT
	class.caption,
	student.sname 
FROM
	student
	INNER JOIN class ON student.class_id = class.cid 
WHERE
	student.sid IN ( SELECT student_id FROM score WHERE num < 60 GROUP BY student_id HAVING count( course_id )>= 2 );
posted @ 2020-05-07 00:18  给你加马桶唱疏通  阅读(156)  评论(0编辑  收藏  举报