目录
- 前言
- 1. 数据库约束
- 1.1. 约束类型
- not null 约束
- unique 唯一约束
- default 默认值约束
- primary key 主键约束
- foreign key 外键约束
- 2. 表的设计
- 2.1. 实体之间的关系
- 一对一
- 一对多
- 多对多
- 3. 新增
- 4. 查询
- 4.1. 聚合查询
- 4.1.1. 聚合函数
- 4.1.2. group by 子句
- 4.1.3. having
- 4.2. 联合查询 (多表查询)
- 4.2.1. 内连接
- 4.2.2. 外连接 (比较少用)
- 4.2.3. 自连接 (比较少用)
- 4.2.4. 子查询 (嵌套查询, 不推荐使用)
- 4.2.5. 合并查询
前言
进阶内容主要是针对查询
新增,修改,删除基本没有进阶的内容了
1. 数据库约束
约束就是创建表的时候,给这个表指定的一些规则,后续插入/修改/删除时都要保证数据是能够遵守这些规则的
引入规则,是为了进行更强的数据检查/校验
1.1. 约束类型
如果后续插入/修改的数据不符合约束类型,就会报错
not null 约束
指示某列不能存储 NULL 值 (从选填项变成必填项)
此时创建了一个没有任何约束的表 student, 可以往里面插入空值,此时我们删除 student 表,重新创建一个有约束的 student 表
此时是对列 id 进行了 not null 约束,对 student 表的描述中, id 中不能为 NULL, 但 name 可以为 NULL
unique 唯一约束
保证某列的每行的值必须是唯一的, unique 可以指定多个列
如果我再插入 id 为 1 , name 为 张三 还是可以的,因为 此时 id 和 name 都没有 unique 约束
有时候我们会期望数据是唯一的,不要重复出现,就可以加上 unique 约束
UNI 就是 unique 的缩写,表示 id 这一列的每个数据都是唯一的
duplicate 重复的
加上 unique 约束之后,后续进行插入/修改的时候,都会先进行查询,看看当前这个值是否已经存在
default 默认值约束
规定没有给列赋值时的默认值
默认情况下的默认值就是 NULL
此时没有给列赋值时的默认值就是 NULL,我们可以用 default 调整默认值,这里我们删除这个表,重新创建一个有 default 约束的表
然后再进行指定列插入
很多时候,如果返回一个 null,是一个不太好的体验,尤其是不应该把 null 这样的字眼呈现到普通用户的眼前
primary key 主键约束
primary key 相当于 not null 约束 和 unique 约束 的结合
主键 是数据库中最重要的约束了
用来作为一个记录的身份标识 (就像身份证号)
注意: 一个表当中只能有一个 primary key
主键是不允许重复的,我们怎么保证我们输入的数据是不重复的呢? mysql 自身给我们提供了一种机制 - 自增主键
当前这个 id 就不需要用户自己指定了,可以交给数据库,让数据库自行进行分配,数据库会按照自增的方式来分配 1,2,3,4…(前提是自增的数据得是整型才能自增)
这里的 id 为什么可以插入 null,被主键约束不是不能为空吗? 插入 null 为了可以让数据库自行分配
这里的 id 手动指定也是可以的
当手动指定了之后,还是要确保,当前你插入的结果是不重复的
每次使用 null 的方式插入自增主键的时候,数据库会根据当前这一列的最大值 的基础上继续进行递增.而且这里的递增是不会重复利用之前的值的,除非手动插入一个 id 为 5 的数据 …
上述自增主键,只能在单个数据库下生效
如果数据库是由多个 mysql 服务器构成的集群,此时,自增主键就无法生效了
foreign key 外键约束
描述的是两个表之间的关联关系,为了方便举例,我们创建两个有关联的表 class 和 student
创建3个班级
创建3个学生
像王五这个同学,不在班级表中存在,因此王五同学就是一个不太科学的非法数据
引入外键约束,就是为了解决这样的问题
希望学生表中的 classId 都要在班级表中存在
就可以使用外键约束进行校验
外键约束与其他约束的写法不同;
其他约束都是那一列需要约束,就创建到那一列的后面,
外键约束,则是写到最后的,把所有前面的列都定义好了之后,在最后面通过 foreign key 创建外键约束
格式是: foreign key (指定本表哪个列) references 其他表名(其他表名中的列)
此时就要求: 本表中这个列的数据必须要在引用的外面的表的对应列中存在
这个例子的意思就是每一个同学的 classId 得在班级表中的 classId 里存在
这个情况下,也可以认为: 班级表约束了学生表
把班级表这种约束别人的表,称为 ‘父表’ (parent table)
把学生表这种被别人约束的表,称为 ‘子表’ (child table)
这个被引用的列 classId ,得带有索引,才能被子表引用,因为 class 表里有了主键约束之后,就会自动创建出索引,能够加快查询速度,要求父表要有主键约束才能引用
constraint 约束
由于此时班级表是空着的,1 这个 classId 一定不在班级表中存在
引入外键约束之后,新增一个记录,就会先在对应的父表中查询,看看是否存在,如果不存在,就会报错
添加或修改不满足约束的数据都会报错
表面上是父表约束子表,实际上是双向约束,如果尝试删除父表中的数据,就可能有问题
想要删除父表中的数据,就必须先删除子表中的对应的数据,确保子表中没有数据引用父表里的数据,才能真正执行删除
2. 表的设计
数据库设计就是根据需求,来把你需要的表给创建出来 (有几个表, 每个表里都有是什么…)
设计原则:
- 先根据需求,找到实体 (像是面向对象,一般每个实体都得安排一个表)
- 梳理清楚实体之间的关系
2.1. 实体之间的关系
实体之间的关系有4种:
- 一对一
- 一对多
- 多对多
- 没关系(两个实体毫不相干)
一对一
模版:
一个学生,可以有一个学生账户
一个学生账户,也只能给一个学生使用
一对多
模版:
一个班级,可以包含多个学生
一个学生,只能从属于一个班级
多对多
模版:
一个学生,可以选择多门课程
一个课程,也可以被多个学生选择
多对多可以再搞个第三个表将两个表联系起来,可以实现多对多的关系
3. 新增
其实是把插入语句和查询语句结合到一起
这里是以查询的结果,来作为插入的值,比如下面的 insert into 表名 select …, 这里的select 代替了 values… ,表示将查询到的结果直接插入到这个表中
这里需要注意查询出来的结果集合,列数和类型需要互相匹配,列名不相同没关系
4. 查询
4.1. 聚合查询
4.1.1. 聚合函数
在之前的基础查询当中有说过表达式查询的例子: select name, chinese + math + english from …
这里的 chinese + math + english 是列与列之间进行运算.
而聚合查询是行与行之间进行运算,此处行之间的运算有一定的限制,不像表达式查询写的那么自由
聚合查询只能通过 SQL 提供的一些聚合函数 (库函数) 来进行操作
下面列举一些最常用的聚合函数:
- count(表达式)
- sum(表达式)
- avg(表达式)
- max(表达式)
- min(表达式)
sum,avg,max,min中的表达式得是数字才有意义
接着还是举个例子来展示聚合函数的使用效果:
举例用的数据源码:
-- 创建考试成绩表
DROP TABLE IF EXISTS exam_result;
CREATE TABLE exam_result (id INT,name VARCHAR(20),chinese DECIMAL(3,1),math DECIMAL(3,1),english DECIMAL(3,1)
);
-- 插入测试数据
INSERT INTO exam_result (id,name, chinese, math, english) VALUES(1,'唐三藏', 67, 98, 56),(2,'孙悟空', 87.5, 78, 77),(3,'猪悟能', 88, 98.5, 90),(4,'曹孟德', 82, 84, 67),(5,'刘玄德', 55.5, 85, 45),(6,'孙权', 70, 73, 78.5),(7,'宋公明', 75, 65, 30);
count() 聚合函数操作就相当于:
- 执行 select * from exam_result
- 再使用 count 来计算结果的行数
注意: 如果查询结果带有 null 值,此时 null 值的记录是不会计入计数的,但在使用 count(*) 的时候,如果有全是 null 的行,也会记录起来
sum会把这一列的值全部在一起进行相加,但是这个过程中,会把 null 给忽略掉
avg, max, min 的用法和 sum 基本类似的
如果我想查询整个表里,语文成绩最低的同学…
大部分情况,聚合的列和非聚合的列,不能在一起配合使用,有一种情况除外 (group by)
一种比较简单直观的写法:
4.1.2. group by 子句
刚才的聚合,是把整个表的所有行,都聚合在一处了
但是有时候,也可以把所有的行,分成若干组,每个组都能分别进行聚合
格式: … group by 列名
效果就是把这个指定的列,把值相同的记录都划分到一组,针对这些组就可以分别进行聚合查询了
分组操作,往往就是和 聚合 配合使用的
这里创建了一个员工表当做例子, 其中 role 列里有些人的岗位都是一样的,有时候就需要通过岗位进行一些查询或者统计相关的工作,这就可以使用 group by 了
比如查询每个岗位的平均薪资是多少
这个 role 是 没有使用聚合函数的列,本来是不能够和 聚合函数 一起使用的,但是 role 被 group by 了
按照 role 来 group by ,所以写 select 列名的时候,可以写 role ,每个组里的 role 都是相同的 ,但是不能写 id / name
4.1.3. having
针对上面的分组查询,也是可以使用条件的
针对的查询结果进行条件筛选:
- 分组之前的条件,使用 where 来表示
- 分组之后的条件,使用 having 来表示
having 和 where 的用法是一样的
比如:
-
统计除了张三之外的人, 每个岗位的平均薪资 , 这里是在分组之前,先把张三除去了,这个条件不必分组就能得到,这个时候就用 where
-
统计每个岗位的平均薪资,刨除平均薪资 >= 20000 的情况, 因为平均薪资必须要分组之后, 才能计算, 这个时候就用 having
-
查询每个岗位的平均薪资,除去 张三,也除去 平均薪资 >= 20000 的情况, 这个时候 where 和 having 就得同时使用了
4.2. 联合查询 (多表查询)
前面的查询,都是针对一张表的
多表查询,就要比单表查询更复杂一些
多表查询是对多张表的数据取笛卡尔积
笛卡尔积的列数,就是之前两个表的列数之和
笛卡尔积的行数,就是之前两个表的行数之积
如果表更多,就可以两两进行笛卡尔积
多表联合查询的核心操作就是进行笛卡尔积
比如使用两个表进行联合查询,就是先把这两个表计算笛卡尔积,然后再指定一些条件,来实现需求中的一些查询结果
实际开发中,笛卡尔积(多表查询) 一定要慎重使用,使用之前一定要评估好笛卡尔积的规模,如果基于两个很大的表进行笛卡尔积,可能就会产生大量的运算和 IO,可能就会把数据库给搞挂了…
下面通过创建两个表来演示:
SQL 中只需使用 select …from 多个表名 就能进行笛卡尔积
在笛卡尔积的基础上, 使用 where 条件表示出来, 得到的结果,就全都是有意义的数据了
像 student.classId = class.classId 这种条件就叫做连接条件,笛卡尔积就相当于把多个表给连接到一起了
在进行多表查询的时候,往往就是:
- 计算笛卡尔积
- 指定连接条件
- 根据需求指定其他条件
- 针对列进行精简/使用聚合函数
为了方便举例多表查询, 这里重新构造4张表:
举例用的题目数据源码:
drop table if exists classes;
drop table if exists student;
drop table if exists course;
drop table if exists score;create table classes (id int primary key auto_increment, name varchar(20), `desc` varchar(100));
create table student (id int primary key auto_increment, sn varchar(20), name varchar(20), qq_mail varchar(20), classes_id int);
create table course(id int primary key auto_increment, name varchar(20));
create table score(score decimal(3, 1), student_id int, course_id int);insert into classes(name, `desc`) values
('计算机系2019级1班', '学习了计算机原理、C和Java语言、数据结构和算法'),
('中文系2019级3班','学习了中国传统文学'),
('自动化2019级5班','学习了机械自动化');
insert into student(sn, name, qq_mail, classes_id) values
('09982','黑旋风李逵','xuanfeng@qq.com',1),
('00835','菩提老祖',null,1),
('00391','白素贞',null,1),
('00031','许仙','xuxian@qq.com',1),
('00054','不想毕业',null,1),
('51234','好好说话','say@qq.com',2),
('83223','tellme',null,2),
('09527','老外学中文','foreigner@qq.com',2);
insert into course(name) values
('Java'),('中国传统文化'),('计算机原理'),('语文'),('高阶数学'),('英文');
insert into score(score, student_id, course_id) values
-- 黑旋风李逵
(70.5, 1, 1),(98.5, 1, 3),(33, 1, 5),(98, 1, 6),
-- 菩提老祖
(60, 2, 1),(59.5, 2, 5),
-- 白素贞
(33, 3, 1),(68, 3, 3),(99, 3, 5),
-- 许仙
(67, 4, 1),(23, 4, 3),(56, 4, 5),(72, 4, 6),
-- 不想毕业
(81, 5, 1),(37, 5, 5),
-- 好好说话
(56, 6, 2),(43, 6, 4),(79, 6, 6),
-- tellme
(80, 7, 2),(92, 7, 6);
下面在创建 classes 表的时候, desc需要加上反引号,因为在 SQL 中, desc 是降序的关键字
这四个表分别是学生, 班级, 课程, 分数
班级和学生是一对多的关系, 学生和课程是多对多的关系
分数表就是课程和同学之间的关联表
4.2.1. 内连接
内连接和外连接的区别在本文章的 4.2.2. 外连接中有说明
以下3个例题都是内连接
例题1: 查询’许仙’同学的成绩
分析: 学生名字在学生表中, 成绩在成绩表中,需要将学生表和成绩表进行联合查询
第一种写法: select * from 表1,表2 where 条件…;
-
笛卡尔积: select * from student, score;
-
指定连接条件: select * from student, score where student.id = score.student_id;
-
根据需求指定其他条件: select * from student, score where student.id = score.student_id and student.name = ‘许仙’;
-
针对列进行精简 / 表达式 / 使用聚合函数: select student.name, score.score from student, score where student.id = score.student_id and student.name = ‘许仙’;
第二种写法: select * from 表1 join 表2 on 条件…;
- 计算笛卡尔积: select * from student join score;
- 指定连接条件: select * from student join score on student.id = score.student_id;
- 根据需求指定其他条件: select * from student join score on student.id = score.student_id and student.name = ‘许仙’;
- 针对列进行精简 / 表达式 / 使用聚合函数: select student.name, score.score from student join score on student.id = score.student_id and student.name = ‘许仙’;
例题2: 查询每个同学的总成绩,以及同学的个人信息
分析:
-
此时成绩是按照 行 的方式排列,要想加和,就需要使用聚合查询了
-
需要每个同学的总成绩,就要按照 同学 的 id 进行 group by (名字可能重名)
解题:
-
计算笛卡尔积: select * from student, score;
-
指定连接条件: select * from student, score where student.id = score.student_id;
-
根据需求指定其他条件: 由于此处是需要所有同学的总成绩,不需要对同学进行进一步的筛选,所以没有此步骤
-
针对列进行精简 / 表达式 / 使用聚合函数: select student.id, student.name, sum(score.score) as total from student, score where student.id = score.student_id group by student.id;
例题3: 查询所有同学的成绩,以及同学的个人信息
分析: 需要 student表, course表, score表 这三张表的连接,要两个连接条件, 观察这三个表发现: score表可以将 student表 和 course 表联系起来
解题:
-
计算笛卡尔积: select * from student, course, score;
-
指定连接条件: select * from student, course, score where student.id = score.student_id and course.id = score.course_id;
-
根据需求指定其他条件: 我们已经通过第二步就可以得到所有的有效信息,这一步省略
-
针对列进行精简 / 表达式 / 使用聚合函数: select student.name as studentName, course.name as courseName, score.score from student, course, score where course.id = score.course_id and student.id = score.student_id;
4.2.2. 外连接 (比较少用)
外连接的实际应用很少
外连接也是 多表查询 的一种体现形式
多数情况下,内连接和外连接,查询结果没啥区别,只有一些特殊情况下,查询结果才会存在差异
这里再创建两个表举例:
student 表 和 score 表 的数据是一一对应的
学生表中的任何一个记录都能在分数表中体现出来
反之,分数表中的每个记录,也能在学生表中体现出来
此时,内连接和外连接的结果都是一样的
内连接的两种写法:
join 前面还可以加上 inner ,inner join 就表示内连接, inner 可以省略
外连接 , 分成 左外连接 (left) 和 右外连接 (right)
在当前数据的情况下,内连接和外连接的结果是一样的
如果对这两个表的数据进行修改,让这两个表的记录不再一一对应,此时内连接和外连接的结果就不同了
此时学生表中的王五没有在成绩表中对应,成绩表中的 4 号学生 id 也没有在学生表中对应 (这种情况属实是不太常见的情况)
内连接的结果: 对于内连接来说,得到的结果,必须是同时在两个表中都存在的数据
外连接的结果:
- 左外连接, 就是以左表为主, 保证左侧的表中的每个记录都体现在最终结果里,如果这个记录在右表中没有匹配,就把对应的列填成 NULL
- 右外连接以右表为主,会保证右表中的每个记录都存在,如果对应的数据在左表中没有,就会填成 NULL
4.2.3. 自连接 (比较少用)
自连接: 同一张表自己和自己进行笛卡尔积
有的时候, 要想进行条件查询, 条件一定是列和列之间, 不能是行和行之间指定条件.
有时候就想针对 行 之间指定条件, 此时就可以通过自连接,把 行 关系 转成 列 关系…
例题: 显示所有 计算机原理的成绩 比 Java成绩 高的成绩信息
分析: 在这个例子中,要想完成不同科目的比较,就需要比较行之间的大小 (在 sql 中无法直接做到), 就只能把行转成列
解题: 先自己和自己进行笛卡尔积
查询的两个表名不能相同,解决办法: 给这个表起不同的别名 (alias)
然后指定条件: 此处要关注的是每个学生自己的两门课的成绩情况,所以就按照学生 id 作为连接条件
然后需要筛选出所有左表为 3 ,右表为 1 的记录 (等价于筛选左表为 1,右表为 3 的记录)
有修java和计算机原理的所有学生都筛选完毕,接下来就是分数的比较
如果要显示名字还要和学生表进行笛卡尔积
最后可以再对列进行精简
4.2.4. 子查询 (嵌套查询, 不推荐使用)
把多个简单的 sql, 合并成一个 复杂 sql
例题: 查询与 ‘‘不想毕业’’ 同学的同班同学
分析: 先找到 ‘‘不想毕业’’ 的班级 id, 再拿着这个 班级id 来进行查询其他同学
以上是正常的做法
因为 classes_id = 1 中的1是从 select classes_id from student where name = ‘不想毕业’; 这个 sql 语句得来的, 可以把这个语句看做一个整体, 替换到 1 这个位置上, 这两个 sql 合并成一个 sql 就是子查询
4.2.5. 合并查询
把两个查询的结果集合,合并成一个集合
使用 union 关键字来完成
格式比如: select 1 union select 2
要求两个 select 查询的结果集, 列数和类型要匹配, 列名不影响,最终的列名就是第一个 select 的列名
例题: 查询 id 小于 3, 或者名字为 ‘英文’ 的课程
如果都是查询同一个表,可以用 or 解决,但是 union 左右两侧 sql 可以查询两个不同的表, or 可不行
union 还会自动对查询结果进行去重
union all 不会去重