数据库表的约束与扩展属性
数据库表的约束:对数据表中某一字段的约束
常见的约束类型
- NOT NULL(非空约束) 表示指定字段必须插入数据,不能为空
- UNIQUE (唯一约束)表示指定字段不能出现两个相同的数据(NULL除外)
- PRIMARY KEY (主键约束)表示指定字段非空且唯一
- FOREIGN KEY(外键约束)当前表中的指定字段的数据受到其他表中指定字段的约束
- DEFAULT (默认约束)表示当没给指定列赋值时该字段会有一个默认值
注意事项:
1、一张表中只能有一个主键,如果没设置主键,会将第一个非空且唯一的字段设置为主键
2、组合主键:在组合主键中所有的主键字段数据相同时,数据才算重复,也就是所id或者name有可能重复,但是不会出现存在2条记录id和name都一样,primary key(id, name)
将id和name设置为组合组件
3、如果将指定字段设置外键约束,则该字段的数据必须再另一个表中存在,例如有一个学生表和班级表,将学生表中的班级id关联到班级表中并设置外键约束,如果在班级表插入班级的id只有1和2,那么学生表中的班级id只能是1和2,否则报错。且在删除时,必须先删除学生表,才能删除班级表
示例:
班级表
create table if not exists classes(
id int primary key,
name varchar(32),
`desc` varchar(100)
);
课程表
create table if not exists course(
id int primary key,
name varchar(32)
);
学生表
create table if not exists student(
id int primary key,
sn int not null unique,
name varchar(32),
qq_email varchar(32),
classes_id int,
course_id int,
foreign key(classes_id) references classes(id),
foreign key(course_id) references course(id)
);
数据库表的扩展属性
- AUTO_INCREMENT (自增属性)这一字段必须是数字,且只能用于主键,每次自增都是++操作
- DEFAULT (默认属性)表示当没给指定列赋值时该字段会有一个默认值,必须是不主动添加数据才会使用默认值
- COMMENT (注释属性)给指定字段添加注释
create table if not exists student(
id int primary key auto_increment comment '学生信息id',
sn int not null unique comment '学号',
name varchar(32) not null default 'unknow' comment '姓名',
qq_email varchar(32) comment 'qq邮箱',
classes_id int comment '所在班级id',
foreign key(classes_id) references classes(id)
);
数据库表的关系
ER图:E-R图也称实体-联系图,提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型
其中矩形框表示的是一个实体;椭圆图框或者圆角矩形是实体的属性;菱形框表示的是实体之间的联系;它们之间会用实线连接起来,并标志联系的类型(1:1,1:n,n:1,n:n)
数据库表的设计
三大范式:
虽然数据库存储数据比较简单,创建一张表,就可以将数据存储进行,但是我们数据的存储或者说是数据库表的设计就需要考虑更多的问题,如果考虑不周就会造成一些问题,例如数据冗余问题、查询性能问题、传输性能问题。三大范式就是用于规范数据库表的设计,减少数据库中数据的冗余,使设计的表更加合理
第一范式:要求表中的每一列都是不可分割的原子性数据
例如在表中,表中的家庭信息和学校信息都是可以分割为单独的一个字段或者列,在家庭信息中,可以分为家庭人口和户籍;在学校信息中,可以分为学历和所在年级
经过转换,下表才是满足第一范式的表
第二范式:确保表中的每一列数据都与主键相关,而不能只与主键的一部分相关(针对组合主键)
在下表中,订单号和产品号组成了组合主键,字段产品数量、产品折扣、产品价格都与主键订单号和产品号有直接关系,而订单金额和订单时间只与组合主键中的订单号相关,标红的订单号对应的订单金额和订单时间存在冗余,所以该表就不满足第二范式,
所以此时可以将订单金额和订单时间分割出去,设置为两张表,使所有字段都与主键有直接的关系
第三范式:确保表中的每一列数据都应该与主键直接相关,而不是间接相关
在下表中,所有属性都依赖于学号,满足第二范式,但是班主任的性别和班主任的年龄并不和主键学号有直接的关系,这两个字段只与班主任姓名存在直接关系,当存在统一班主任时,就会导致班主任性别和班主任年龄存在数据冗余,所以不满足第三范式
调整后如下吗,将没有直接关系的字段分割出去,创建两张表
分组查询
分组查询:以表中某一字段作为分组依据对表中数据进行统计查询
例如表中有学生id,学生姓名,语文分数和数学分数,要想获得语文的平均分,就要以语文分数作为分组依据,对成绩进行统计查询
聚合函数:是mysql向用户提供的一些简便的函数
函数 | 说明 |
---|---|
count() | 统计所查到的记录的数量 |
sum() | 对表中指定数值字段进行求和 |
avg() | 对表中指定数值字段求平均值 |
max() | 对表中指定数值字段找出最大数值 |
min() | 对表中指定数值字段找出最小数值 |
示例:
//表结构
create table if not exists emp(
id int primary key auto_increment,
name varchar(20) not null,
role varchar(20) not null,
salary numeric(11,2)
);
//表数据
insert into emp(name, role, salary) values
('马云','服务员', 1000.20),
('马化腾','游戏陪玩', 2000.99),
('孙悟空','游戏角色', 999.11),
('猪无能','游戏角色', 333.5),
('沙和尚','游戏角色', 700.33),
('隔壁老王','董事长', 12000.66);
//统计薪资大于1000的员工个数
select count(*) from emp where salary >= 1000;
//统计所有员工的薪资总和
select sum(salary) from emp;
//求所有员工薪资的平均值
select avg(salary) from emp;
//求员工中最高的薪资
select max(salary) from emp;
//求员工中最低的薪资
select min(salary) from emp;
分组查询时,查询的信息只能是分组依据字段或者是聚合函数,统计数据就是根据依据字段进行统计的,例如在以下例子中,查询角色的平均薪资,并不是查询一个人的,所以就不能查询人的名字;想要分组查询时条件过滤,是不能使用where关键字的,要使用having关键字
//查询全部角色的平均工资
select role, avg(salary) from emp group by role;
//查询平均薪资大于1000的角色
select role, avg(salary) from emp group by role having avg(salary)>1000;
联合查询(多表联查)
联合查询:将多表中的数据合到一起查询数据
将多表进行合一的方式:笛卡尔积
数据库多表数据合一方式:内连接、左连接、右连接、自连接
内连接:inner join ,其合一方式是取两张表的交集
左连接:left join,以左表的数据作为基表数据,在右表中连接符合条件的数据,找不到以NULL展示
右连接:right join,以右表的数据作为基表数据,在左表中连接符合条件的数据,找不到以NULL展示
自连接:inner join,将自己的表进行连接,需要对表明进行别名显示
示例
//四张表
//课程表
CREATE TABLE course (
id INT PRIMARY KEY auto_increment,
name VARCHAR(20)
);
//班级表
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 INT UNIQUE,
name VARCHAR(20) DEFAULT 'unkown',
qq_mail VARCHAR(20),
classes_id int,
FOREIGN KEY (classes_id) REFERENCES classes(id)
);
//成绩表
CREATE TABLE score (
id INT PRIMARY KEY auto_increment,
score DECIMAL(3, 1),
student_id int,
course_id int,
FOREIGN KEY (student_id) REFERENCES student(id),
FOREIGN KEY (course_id) REFERENCES course(id)
);
//数据
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);
//查询白素贞同学的成绩
select sco.score
from student stu
inner join score sco
on stu.id=sco.student_id and stu.name='白素贞';
//查询白素贞同学同学的Java成绩及个人信息
select stu.name, cou.name,sco.score
from student stu
--将三个表进行连接
inner join score sco on sco.student_id=stu.id
inner join course cou on sco.course_id=cou.id
--过滤
where stu.name='白素贞' and cou.name='Java';
//查找Java成绩比计算机原理成绩低的学生
select sco1.student_id,sco1.score Java,sco2.score "计算机原理"
from score sco1 join score sco2
--保证连接在一起的成绩数据都是同一个学生的
on sco1.student_id=sco2.student_id
where sco1.course_id=1 and sco2.course_id=3
and sco1.score<sco2.score;
子查询
子查询:查询的条件是另一条语句的结果
示例:
//查询与许仙同班的同学的信息
select* from student
where classes_id
=(select classes_id from student where name='许仙');
[not] IN
SELECT * FROM A WHERE id IN (SELECT id FROM B);
等价于:
1、SELECT id FROM B ----->先执行in中的查询
2、SELECT * FROM A WHERE A.id = B.id
in:适合外部表数据大于子查询的表数据的业务场景
[not] EXIST
select * from A a where exists(select 1 from B b where a.id=b.id)
以上查询等价于:
1、SELECT * FROM A;
2、SELECT I FROM B WHERE B.id = A.id;
exist适合 子查询中表数据大于外查询表中数据的业务场景
union
合并查询:该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行
or和union的区别:or这个逻辑运算符会忽略索引,在海量数据查询中会降低查询性能
//查询课程id小于3,或者名称等于英文的课程
select * from course where id < 3 union select * from course where name='英文';
select * from course where id < 3 or name='英文';
索引
索引:类似于数据库中的数据来说类似于一本书的目录,通过目录能够快速找到某一章节的内容所在页,而通过索引能够快速找到数据所在位置
功能:
- 作用类似书籍目录,可用于快速定位、检索数据
- 当进行海量数据的查询时,索引可以提高查询的性能
适用场景:
- 数据量较大,且经常对这些列进行条件查询
- 该数据库表的插入操作,及对这些列的修改操作频率较低
- 索引会占用额外的磁盘空间,会创建额外的索引信息表,磁盘空间够大
不适用场景:如果非条件查询列,或经常做插入、修改操作,或磁盘空间不足时,不考虑创建索引
SQL命令示例:
//查看学生表的已有的索引
show index from student;
//创建班级表中的name字段的索引
create index idx_classes_name on classes(name);
//删除班级表中的name字段的索引
drop index idx_classes_name on classes
事务
事务:类似于原子操作,指数据库逻辑上的一组操作,组成这个操作的各个单元,要么全部执行成功,要么全部执行失败
功能:保证数据的多个操作合并为一个原子操作,若操作过程中出现异常,则可以事务回滚,回滚到事务开启之前
例如你在取钱的时候,账户银行卡已经扣掉了钱,由于网络波动,或者是数据库挂掉,取钱机并没有吐钱,这时候就是不合理的,所以我们应该将账户金额扣钱和吐钱绑定在一起,要门全部成功,要么全部失败
假设舍友b要偷舍友a的前,那么舍友a的前减少和舍友b的钱增多应该是原子操作,所以可以用事务来控制
create table accout(
id int primary key auto_increment,
name varchar(20) comment '账户名称',
money decimal(11,2) comment '金额'
);
insert into accout(name, money) values
('舍友A', 5000),
('舍友B', 1000);
//假如我们开启事务后
start transaction;
//执行了第一条SQL语句,由于网络错误,第二条SQL语句并没有执行
update accout set money=money-2000 where name = '舍友A';
//此时就可以使用事务回滚,将事务开启之后的所有操作都退回开启事务之前
rollback;
//开启事务
start transaction;
update accout set money=money-2000 where name = '舍友A';
update accout set money=money+2000 where name = '舍友B';
//提交事务,提交事务后就无法回滚
commit;