Day18 多表查询
为了消除数据的冗余,我们存数据的时候,会把完整的数据信息,分散到多张表来存储。但是,用户通常需要的是一个完整的全面的信息,那么在查询的时候,就需要把这些分散的信息 拼接 。——多表查询
“拼接” 《=》 连接
连接查询
交叉连接:(笛卡尔积)
cross join将两张表的信息结合在一起(表1 | 表2 ):
select * from 表1 cross join 表2(on );
或者隐式的写法 : select * from 表1,表2;
笛卡尔积:
实现两张表的拼接,并不确保数据的正确性。
拼接过程:
1.对于两张表中的数据,不管有没有关系, 全部 两两组合。
内连接:
概述:
返回连接表中 符合连接条件 及 查询条件的 数据行。比之于交叉连接,更加关注参与连接的数据的内容。
显式内连接: inner join + on
select * from + 表1重命名 + inner join关键字 + 表2重命名 + on 判断条件select * from customer c inner join orders o on c.id=o.customer_id;
customer c : 给customer 表重命名 / orders o :给orders 表重命名为o
inner join : 内连接方式拼接左右两表。
on : 判断连接条件 (重命名的 c 表的id属性,是否等于 重命名的o表的customer_id)
这里的 on 的判断条件需要在两个表中对应好。
(这里重命名的 c 和 o 可以看成是对表的引用)
隐式内连接: inner join + where
select * from customer c,orders o where c.id=o.customer_id;外链接:
概述:
会将 所有人的 情况 都会显式出来,如果不满足条件,就会以null的形式显式比之于内连接:内连接只返回符合条件的数据行,而外链接,就会将以基准表的所有信息都显示出来
左外链接:
left outer join 关键字,在 on 子句中设定连接条件。select * from customer c left outer join orders o on c.id=o.customer_id;
效果:以左边为基准表,左表的每一行数据都必须出现在结果集当中,
1.如果满足连接条件,就正常拼接;
2.如果不满足连接条件,那么左边的不满足连接条件的数据记录就会放在结果集的最后,并且左表数据放入其中,右表数据全为NULL;
右外链接:
right outer join 关键字(连接两表),在 on 子句中设定连接条件。效果:以右表为基准表,右表的每一行数据都必须出现在结果集中。
与左外连接只是表的基准不一样。
在连接之后还可以 + where 来进行新表的条件过滤。
eg. select * from customer c left outer join orders o on c.id=o.customer_id where price>200;
where 子句的工作模式:
作用:筛选和过滤;筛选的对象:表 中的每一行数据。
遍历表中的每一行数据,然后根据过滤条件,对每一行数据记录进行判断(true/false)
如果过滤结果为true,就加入结果集中。
*****在关系数据库中,所有的数据都存在一张二维表中;所以表的 拼接 、 查询的结果都是一张二维表。
(所以where 就能够作用在 所有的二维表中,只要where前面的代码 表示的是一张二维表。)
嵌套查询(子查询):详见后面
概述:是指在 where 子句 或 from 子句中 又嵌入 select。分类:
相关子查询 : 依赖于所生成的父表。
不相关子查询 : 不依赖与所生成的父表。
select * from orders where customer_id={select id from customer where name like '%郭靖%'};
{}中得到的就是符合条件的另一张表的id属性。
相当于在两张表中分别进行查询。
SQL 注释:
单行注释:-- 这里写注释 --
多行注释: /* */
联合查询:
关键字:union求两张表的 并集。(MySQL中没有实现求 交集和差集的 方法)
联合查询能够合并两条查询语句的查询结果,去掉其中的重复数据行,然后返回查询结果。
前提是两张表的结构要相同。
语法:表1 union 表2;
(合并方式:会先将表1放入结果集,然后再查询表2的数据,没有重复的就放入结果集)
select * from
报表查询:
对数据进行 分组(group by) 统计(统计函数)。关键字 :group by + (having)
统计函数:
都是针对 列 来进行操作的。
1.count() : 统计某列的元素个数(即行数(满足某些条件的行)).
select count(*) from 表1; //得到表1 的行数
加入条件: select count(math) from 表1 where math>90;
返回的表的属性就是 : count(math)
count(只能是一个属性)
2.sum():对一列数据求和
只能对数值求和,如果是字符串就会报错。可对多列求和:','
select sum(math) from 表1 where sex='男';
select sum(math),sum(chinese)+sum(english) from 表1; //对math、english+chinese 的成绩进行统计
3.avg():对一列求平均数。
select avg(math) from 表1;
4.max():返回某列中的最大值
select max(math) from 表1;
5.min():返回某列中的最小值
select min(math),max(math),avg(math),count(math),sum(math) from 表1;
group by :
表示按照什么属性列来分组。
统计函数在加入了group by语句之后,其作用范围就变成了各个分组。
having : 过滤分组条件。
select product,sum(price) from 表1 group by product;
其中sum函数的作用范围就变成了每个分组,对每个分组的price列进行求和。
select product,sum(price) from 表1 group by having sum(price)>100
having 子句中加入过滤条件。(可以使用SQL语句)
having 和 where 的区别:
having 作用于一列数据(可以使用统计函数和SQL语句)
where 作用于一行数据(不能使用统计函数)
两者都能实现过滤,在某些情况下可以相互替换,但是where过滤的效率 >> having(远高于having)
因为对于having 来讲,必须首先先分组,再过滤,分组非常消耗时间。
数据库的备份和恢复:(逻辑备份)
备份:
cmd命令行下:(不是 mysql> 下)mysqldump -uroot -p密码 要备份的数据库名称>备份到哪个文件d:\test.sql;
恢复:
1.cmd命令下:mysql -uroot -p密码 要恢复的数据库名称<要恢复数据的文件d:\test.sql;
2.mysql>下:
1.先创建一个新的数据库:
create database 数据库1;
use 数据库1;
2.恢复哪个文件
然后在命令行输入:source d:\test.sql;
补充: 嵌套查询的难点:
相关子查询:select sname,score from sc,student where sc.sid=student.sid and score>(
select avf(score) from sc where sid=student.sid
);
执行父查询中的第一行:组合新表,先求过滤条件,发现要先求 子查询:嵌套()中的 sid的值和 外面的student.sid是否相同。
执行父查询的第二行:
所以子查询中的student.sid是依据父查询的结果来进行。
不相关子查询:
习题:
1.
某大学研究生院有若干研究生导师,包括职工编号、姓名、职称、研究方向,其中每个导师的职工编号是唯一的。
若干研究生,包括学号、姓名、性别、入学日期,其中每个研究生的学号是唯一的。
每个导师可以带若干研究生,但每个研究生只能有一个导师。
请设计一个数据库,要求可以正确体现导师和研究生之间的关系。
设计完毕之后,请插入一定量的数据,并验证你设计的数据库是否满足要求。
CREATE DATABASE sql2;
USE sql2;
CREATE TABLE teacher(id INT PRIMARY KEY,NAME VARCHAR(20),PT VARCHAR(20),way VARCHAR(20));
INSERT INTO teacher VALUES(1,'张飞','益德','丈八蛇矛'),(2,'关羽','云长','青龙偃月刀'),(3,'赵云','子龙','亮银枪')
INSERT INTO teacher VALUES(4,'赵子龙的儿子','不知道','亮银枪')
CREATE TABLE students(sid INT PRIMARY KEY,sname VARCHAR(20),sdate DATE,sex VARCHAR(20),teacher_id INT);
INSERT INTO students VALUES(11,'小白','2000-02-05','男',3),(12,'小黑','2000-03-08','男',2),(13,'小花','2001-09-12','女',2),(14,'小团','2002-09-28','女',1),(15,'小球','2008-09-25','男',2)
INSERT INTO students VALUES(16,'蛋蛋','2002-05-05','男',4)
DESC teacher;
SELECT * FROM teacher;
SELECT * FROM students;
DESC students;
SELECT * FROM teacher LEFT OUTER JOIN students ON students.`teacher_id`=teacher.`id` ORDER BY id DESC;
/*
1.请查出每个导师所带研究生的姓名。
2.清查出特定姓名的导师所带研究生的姓名。
3.请查出每个导师所带研究生的数量。
4.请查出每个导师所带的男研究生的数量。
5.请找出选择哪个研究方向的导师最多。
6.请找统计不同职称的导师的个数。
*/
-- 1.请查出每个导师所带研究生的姓名。 --
SELECT NAME,sname FROM teacher LEFT OUTER JOIN students ON students.`teacher_id`=teacher.`id` ORDER BY id DESC
-- 2.清查出特定姓名的导师所带研究生的姓名。 --
SELECT NAME,sname FROM teacher INNER JOIN students ON students.`teacher_id`=teacher.`id` AND NAME='关羽';
-- 3.请查出每个导师所带研究生的数量。 --
SELECT COUNT(NAME),NAME FROM (SELECT NAME,sname FROM teacher LEFT OUTER JOIN students ON students.`teacher_id`=teacher.`id` ORDER BY id DESC) AS teacher GROUP BY NAME;
-- 4.请查出每个导师所带的男研究生的数量。 --
SELECT NAME,sname,sex,COUNT(sname) FROM (SELECT NAME,sname FROM teacher LEFT OUTER JOIN students ON students.`teacher_id`=teacher.`id` WHERE students.`sex`='男' ORDER BY id DESC) AS teacher GROUP BY teacher.`name`;
-- 5.请找出选择哪个研究方向的导师最多。 --
SELECT COUNT(*) FROM (SELECT way,COUNT(way) AS ways FROM teacher GROUP BY way ) ;
-- 6.请统计不同职称的导师的个数。 --
select a.PT,count(*) from teacher a group by a.PT
2.
在数据库中创建如下两张表
create table dept ( deptno varchar(10) primary key, dname varchar(10) );
create table emp ( empno varchar(10) primary key, ename varchar(10), job varchar(10),
mgr varchar(10), sal float, deptno varchar(10) references dept(deptno) );
insert into dept values ('1','事业部');
insert into dept values ('2','销售部');
insert into dept values ('3','技术部');
insert into emp values ('01','jacky','clerk','tom','1000','1');
insert into emp values ('02','tom','clerk','','2000','1');
insert into emp values ('07','biddy','clerk','','2000','1');
insert into emp values ('03','jenny','sales','pretty','600','2');
insert into emp values ('04','pretty','sales','','800','2');
insert into emp values ('05','buddy','jishu','canndy','1000','3');
insert into emp values ('06','canndy','jishu','','1500','3');
select * from dept;
select * from emp;
-- 1列出emp表中各部门的部门号,最高工资,最低工资 --
SELECT a.`deptno`,MAX(a.`sal`),MIN(a.`sal`) FROM emp a GROUP BY a.`deptno`
-- 2 列出emp表中各部门job为'CLERK'的员工的最低工资,最高工资 --
SELECT job,MIN(sal),MAX(sal) FROM emp WHERE job='clerk' ;
-- 3 对于emp中最低工资小于2000的部门,列出job为'CLERK'的员工的部门号,最低工资,最高工资 --
SELECT deptno,MAX(sal),MIN(sal) FROM emp WHERE 2000>(SELECT MIN(sal) FROM emp) AND job='clerk'
-- 4 根据部门号由高而低,工资有低而高列出每个员工的姓名,部门号,工资 --
SELECT ename,job,sal FROM emp ORDER BY deptno DESC,sal;
-- 5 列出'buddy'所在部门中每个员工的姓名与部门号 --
SELECT ename,deptno FROM emp WHERE job='jishu'
-- 6 列出每个员工的姓名,工作,部门号,部门名 --
SELECT ename,job,emp.`deptno`,dept.`dname` FROM emp LEFT OUTER JOIN dept ON emp.`deptno`=dept.`deptno`
-- 7列出emp中工作为'CLERK'的员工的姓名,工作,部门号,部门名 --
SELECT ename,job,emp.`deptno`,dept.`dname` FROM emp LEFT OUTER JOIN dept ON emp.`deptno`=dept.`deptno` WHERE job='clerk'
-- 8对于emp中有管理者的员工,列出姓名,管理者姓名(管理者外键为mgr) --
SELECT ename,mgr FROM emp WHERE mgr!='';
-- 9 对于dept表中,列出所有部门名,部门号,同时列出各部门工作为'CLERK'的员工名与工作 --
SELECT emp.`deptno`,dname,ename,job FROM dept INNER JOIN emp WHERE emp.`deptno`=dept.`deptno`AND job='clerk'
-- 10对于工资高于本部门平均水平的员工,列出部门号,姓名,工资,按部门号排序 --
SELECT deptno,ename,sal FROM emp AS a WHERE sal>(SELECT AVG(sal) FROM emp WHERE emp.`deptno`=a.`deptno`)ORDER BY deptno;
-- 11对于emp,列出各个部门中工资高于本部门平均工资的员工数和部门号,按部门号排序 --
SELECT deptno,COUNT(*) FROM emp AS emp1 WHERE sal>(SELECT AVG(sal) FROM emp WHERE emp.`deptno`=emp1.`deptno`)GROUP BY deptno ORDER BY deptno;
练习3(难)
CREATE TABLE t_stu (
sid INT PRIMARY KEY AUTO_INCREMENT,
sName VARCHAR(10)
);
INSERT INTO `t_stu`(`sid`,`sName`) VALUES ( NULL,'zs');
INSERT INTO `t_stu`(`sid`,`sName`) VALUES ( NULL,'lisi');
INSERT INTO `t_stu`(`sid`,`sName`) VALUES ( NULL,'wangwu');
CREATE TABLE t_course (
cid INT PRIMARY KEY AUTO_INCREMENT,
cName VARCHAR(20)
);
INSERT INTO `t_course`(`cid`,`cName`) VALUES ( NULL,'数据库概论');
INSERT INTO `t_course`(`cid`,`cName`) VALUES ( NULL,'离散数学');
INSERT INTO `t_course`(`cid`,`cName`) VALUES ( NULL,'算法设计');
CREATE TABLE t_sc1 (
sid INT,
cid INT,
score INT,
CONSTRAINT PRIMARY KEY(sid,cid),
CONSTRAINT sid_fk FOREIGN KEY (sid) REFERENCES t_stu(sid),
CONSTRAINT cid_fk FOREIGN KEY (cid) REFERENCES t_course(cid)
);
INSERT INTO t_sc1(sid,cid,score) VALUES ( 1,1,90);
INSERT INTO t_sc1(sid,cid,score) VALUES ( 1,2,91);
INSERT INTO t_sc1(sid,cid,score) VALUES ( 1,3,92);
INSERT INTO t_sc1(sid,cid,score) VALUES ( 2,1,80);
INSERT INTO t_sc1(sid,cid,score) VALUES ( 2,2,73);
INSERT INTO t_sc1(sid,cid,score) VALUES ( 3,3,77);
表t_course: 表t_sc1: 表t_stu:
需求:
1.查询出选择了所有课程的学生的姓名
方法一:
SELECT sName FROM t_stu stu,t_sc1 sc1 WHERE stu.sid=sc1.sid GROUP BY sName HAVING COUNT(sName)=3 ;
方法二:
SELECT sName FROM t_stu X WHERE NOT EXISTS(
SELECT * FROM t_course Y WHERE NOT EXISTS (
SELECT * FROM t_sc1 WHERE sid=x.sid AND cid=Y.cid
)
);
2.查询选择了2号同学所选择所有课程的同学的名字。
SELECT sName FROM t_stu X WHERE NOT EXISTS(
SELECT * FROM t_sc1 Y WHERE sid=2 AND NOT EXISTS (
SELECT * FROM t_sc1 WHERE sid=x.sid AND cid=Y.cid AND sid <>2
)
);
3.查询选择了和2号同学相同课程的同学的名字
第一种做法
SELECT sName FROM t_stu X WHERE NOT EXISTS(
SELECT * FROM t_sc1 Y WHERE sid=2 AND NOT EXISTS (
SELECT * FROM t_sc1 WHERE sid=x.sid AND cid=Y.cid AND sid <>2
)
) AND NOT EXISTS(
SELECT * FROM t_sc1 a WHERE sid= X.sid AND NOT EXISTS(
SELECT * FROM t_sc1 WHERE sid=2 AND cid=a.cid
)
);
第二种做法
select sname from t_stu X inner join t_sc1 Z on X.sid=Z.sid where not exists(
select * from t_sc1 Y where sid=2 and not EXISTS (
SELECT * FROM t_sc1 WHERE sid=x.sid AND cid=Y.cid AND sid <>2
)
) group by X.sid having count(X.sid) = (select count(*) from t_sc1 where sid=2)
因为没有人和2号同学选择的课程完全相同。