正文
【第一章】
1.1 MySQL学习路线
基础阶段:MySQL数据库的基本操作(增删改查),以及一些高级操作(视图、触发器、函数、存储过程等)。
优化阶段:如何提高数据库的效率,如索引,分表等。
部署阶段:如何搭建真实的环境系统,如服务器集群,负载均衡等。
1.2 数据库的基础
1.2.1 什么是数据库?
数据库:database,存储数据的仓库。
数据库(专业定义):高效的存储和处理数据的介质(介质主要是两种:磁盘和内存)。
1.2.2 数据库的分类?
基于存储介质的不同
关系型数据库(SQL)
非关系型数据库(NoSQL)
1.2.3 不同的数据库阵营中的产品有哪些?
关系型数据库:
大型数据库:Oracle、DB2。
中型数据库:SQL Server、MySQL等。
小型数据库:access等。
非关系型数据库:
memached
mogodb
redis
1.2.4 两种数据库阵营的区别?
关系型数据库:安全(保存磁盘基本不可能丢失),容易理解,比较浪费空间。
非关系型数据库:效率高,不安全(断电会丢失)。
1.3 关系型数据库
1.3.1 什么是关系型数据库?
关系型数据库:是一种建立在关系模型(数学模型)上的数据库。
关系模型:一种所谓建立在关系的模型,关系模型包括以下三个方面:
数据结构:数据存储的问题,二维表(有行和列)。
操作指令集合:所有SQL语句。
完整性约束:表内数据约束,表与表之间约束(外键)。
1.3.2 关系型数据的设计?
从需要存储的数据需求中分析,如果是一类数据(实体,比如人、书本等)应该设计成一张二维表,表是由表头(字段名)和数据部分(实际存储的数据单元)组成,如下图所示。
表头
字段1
字段2
数据单元
数据1
数据2
以实际案例来进行处理:分析一个教学系统,讲师负责教学,教学生,在教室教学。
①找出系统中的实体:讲师表,学生表,班级表。
②找出实体中应该存在的数据信息:
讲师:姓名,性别,年龄,身高,工资等。
学生:姓名,性别,学好,学科等。
班级:班级名字,教室编号等。
关系型数据库:维护的是实体内部,实体与实体之间的联系。
实体内部的联系:每个学生都有姓名,性别,学号,学科,年龄等信息。
姓名
性别
学号
学科
年龄
张三
男
001
java
23
李四
男
002
MySQL
王二
003
Linux
17
第二行的所有字段,都是描述张三这个学生(内部联系);第二列只能存放性别(内部约束)。
关系型数据库的特点之一:如果表中对应的某个字段没有值(数据),但是系统依然需要分配空间,所以关系型数据库比较浪费空间。
实体与实体之间的联系:每个学生属于某个班级,每个班级一定有多个学生(一对多)。
学生表:
姓名
性别
学号
学科
年龄
张三
男
001
java
23
李四
男
002
MySQL
王二
003
Linux
17
班级表:
班级名称
教室编号
java001
A001
Linux002
B005
解决方案:在学生表中增加一个班级字段来指向班级(必须能够唯一的找到一个班级信息)
姓名
性别
学号
学科
年龄
所属班级
张三
男
001
java
23
java001
李四
男
002
MySQL
MySQL005
王二
003
Linux
17
Linux002
学生实体与班级实体之间的关系:实体与实体之间的关系。
1.4 关键字说明
数据库:database。
数据库系统:database system(DBS):是一种虚拟系统,将多种内容关联起来的称呼。DBS=DBMS+DB。
DBMS:数据库管理系统,database management system,专门管理数据库。
DBA:数据库管理员,database administrator。
行/记录:row/record,本质是一个东西,都是指表中的一行(一条记录)。行是从结构角度,记录是从数据角度出发。
列/字段:column/Field,本质是一个东西。
1.5 SQL
SQL:Structured Query Language,结构化查询语言(数据主要以查询为主)。
SQL分为三个部分:
DDL:Data Definition Language,数据定义语言。用来维护存储数据的结构(数据库,表),代表指令:create、drop、alter等。
DML:Data Manipulation Language,数据操作语言,用来对数据进行操作(数据表中的内容),代表指令:Insert、delete、update等。其中DML内部又单独进行了一个分类:DQL(Data Query Language:数据查询语言,如select)。
DCL:Data Control Language,数据控制语言,主要负责权限管理,代表指令:grant、revoke等。
SQL是关系型数据库的操作指令,SQL是一种约束,但不强制(类似于W3C),所以不同的关系型数据库产品(Oracle,MySQL等)内部可能有一些细微的差别。
1.6 MySQL数据库
MySQL数据库是一种C/S结构的软件:客户端/服务端,如果想访问服务器必须通过客户端(服务器一直运行,客户端在需要使用的时候运行)。
交互方式:
①客户端连接认证:连接服务器,认证身份。
②发送SQL指令。
③服务器接收SQL指令,处理SQL指令,返回操作结果。
④客户端接收结果,显示结果。
⑤断开连接(释放资源:服务器并发限制)。
1.7 MySQL服务器对象
没有办法完全了解服务器内部的内容:只能粗略的去分析数据库服务器的内部结构。
将MySQL服务器内部对象分成了四层:
系统(DBMS)
数据库(DB)
数据表(Table)
字段(Field)
1.8 SQL的基本操作
基本操作:CRUD。
将SQL的基本操作根据操作对象进行分类,分为三类,如下所示:
库操作。
表操作(字段)。
数据操作。
1.9 库操作
对数据库的增删改查。
1.9.1 新增数据库
基本语法
create database 数据库名字 [库选项];
库选项:用来约束数据库,分为两个选项。
字符集设定:charset/character set 具体字符集(数据存储的编码格式)。
校对集设定:collate 具体校对集(数据比较的规则)。
示例:创建一个数据库。
-- 单行注释,也可以用#
# 创建数据库
CREATE DATABASE mydatabase CHARACTER SET utf8;
其实,数据库名字不能使用关键字(已经被使用的字符)或保留字(将来可能会使用的字符)。
当创建数据库的SQL语句执行之后,发生了什么?
①在数据库系统中,增加了对应的数据库信息。
②会在保存数据的文件夹下:Data目录,创建一个对应数据库名字的文件夹。
③每个数据库下都有一个opt文件:保存了库选项。
1.9.2 查看数据库
基本语法
①查看所有数据库。
-- 查看所有数据库
show databases;
②查看指定部门的数据库:模糊查询。
-- 查看指定部分的数据库
show databases like 'pattern'; -- pattern是匹配模式
%:表示匹配多个字符
_:表示匹配单个字符
③查看数据库的创建语句。
-- 查看数据库的创建语句
show create database 数据库名字;
示例:查看所有数据库
示例:查看指定名称为test的数据库。
示例:查看含有t名称的数据库。
示例:显示mydatabase数据库的创建语句。
1.9.3 更新数据库
数据库名字是不可以改变的,不然如何定位哪一个数据库,换句话说,原来有一个数据库名为test,你现在将其它数据库改为test,这样好吗?
数据库的修改仅限库选项:字符集和校对集(校对集依赖于字符集)。
基本语法
alter database 数据库名字 [库选项]。
character set [字符集]
collate 绞对集
示例:修改mydatabase数据库的默认编码。
1.9.4 删除数据库
所有的操作中:删除是最简单的。
基本语法
-- 删除数据库
drop database 数据库名字;
示例:删除mydatabase数据库。
当删除数据库语句执行之后,发生了什么?
在数据库内部看不到对应的数据库。
在对应的数据库存储的文件夹内,数据库名字对应的文件夹也被删除(级联删除:里面的数据库表一起被删除)。
注意:数据库的删除不是闹着玩的,不要随意的删除,应该先进行备份操作,然后才考虑是否删除(删除不可逆)。
1.10 表操作
表和字段是密不可分的。
1.10.1 新增数据表
基本语法
-- 新增数据表
create table [if not exists] 数据表名字(
字段名字 数据类型,
字段名字,数据类型 -- 最后一行不不要逗号
)[表选项];
if not exists:如果表名不存在,那么就创建,否则不执行创建。
表选项:控制表的表现。
字符集:charset/character set 具体字符集;--保证表中数据存在的字符集。
校对集:collate 具体校对集。
存储引擎:engine 具体的存储引擎(innodb和myisam)
示例:创建一个名为student的表。
任何一个表的设计都必须指定数据库。
解决方案:
①显示指定表所在的数据库。
-- 显示的指定表所在的数据库
create table 数据库名.表名(
字段1 数据类型,
字段2 数据类型
);
②隐式的指定表所属数据库:先进入到某个数据库环境,然后这样创建的数据库表自动归属到某个数据库。
-- 进入到数据库环境
use 数据库名字;
-- 创建数据表
create table 数据表名(
字段1 数据类型,
字段2 数据类型
);
当创建数据库的SQL指令执行之后,到底发生了什么?
指定数据库下已经存在对应的表。
在数据库对应的文件夹下,会产生对应表的结构文件(和存储引擎有关)。
1.10.2 查看数据表
数据库能查看的方式,表都可以查看。
基本语法
查看所有表
-- 查看所有表
show tables
查看部分表
-- 查看部分表
show tables like 'pattern';
查看表的创建语句
-- 查看表的创建语句
show create table 表名;
查看表结构:查看表中的字段信息
-- 查看表结构
①desc 表名;
②describe 表名;
③show columns from 表名;
示例:查看所有表
示例:查看创建student表的语句。
示例:查看表结构。
1.10.3 修改数据表
表本身存在,还包含字段,所以表的修改分为两个部分:修改表本身和修改字段。
修改表本身:
表名
基本语法:
-- 修改表名
rename table 老表名 to 新表名;
示例:将student表的名字改为stu.
表选项:字符集,校对集和存储引擎。
alter table 表名 [表选项];
示例:修改stu表的编码为gbk
修改字段
新增字段
alter table 表名 add [column] 字段名 数据类型[列属性][位置];
位置:字段名可以存在表中的任意位置
first:第一个位置
after:在哪个字段之后,after 字段名,默认在最后
修改字段
alter table 表名 modify 字段名 数据类型[列属性] [位置];
重命名字段
alter table 表名 change 旧字段名 新字段名 数据类型[列属性][位置];
删除字段
alter table 表名 drop 字段名;
1.10.4 删除数据表
基本语法
-- 删除表
drop table 表名1,表名2,……;
当删除数据表的指令执行之后发生了什么?
①在表空间中,没有了指定的表(数据也没有了)。
②在数据库对应的文件夹下,表对应的文件(与存储引擎有关)也会被删除。
1.11 数据操作
1.11.1 新增数据
有两种方案
①给全表字段插入数据,不需要指定字段列表:要求数据的值出现的位置必须和表中设计的字段出现的顺序一致,凡是非数值的数据,都需要使用引号包裹。
insert into 表名 values (值列表)[,(值列表)]; -- 可以一次性插入多条记录
②给部分字段插入数据:需要选定字段列表,字段列表出现的顺序和字段的顺序无关,但是值列表的顺序必须和选定的字段的顺序一致。
insert into 表名 (字段列表) values (值列表);
1.11.2 查看数据表
基本语法
select */字段列表 from 表名 [where 条件] ;
示例:查看学生的所有信息
示例:查看学生的姓名和性别
1.11.3 更新数据
基本语法
update 表名 set 字段1=值1,字段2=值2,……[where 条件];
示例:更新id=1的年龄为32
1.11.4 删除数据
基本语法
delete from 表名 [where 条件];
示例:删除name为zhangsan的学生信息
1.12 中文数据问题
中文数据问题本质是字符集问题。
计算机只识别二进制,人类更多的是识别符号,所以需要有个二进制和字符的对应关系(字符集)。
客户端向服务器插入中文数据:没有成功
原因:\xD5\xC5\xC8\FD 代表的是“”张三”在当前编码(字符集)下的二进制转换为十六进制,两个汉字-->四个字节(GBK)。
报错:服务器没有识别对应的四个字节,服务器认为数据是utf8的,一个汉字对应三个字节;所以服务器读取三个字节转换成汉字,失败了;剩余的再读三个字节,最终失败。
所有的数据库服务器认为(表现)的一些特性是通过服务器端的变量来保存的,系统先读取自己的变量,看看应该怎么表现。
查看服务器识别的所有字符集
show character set;
服务器默认的和客户端打交道的字符集。
show variables like 'character_set%';
问题根源:客户端数据只能是GDK,而服务器认为是utf8。
解决方案:改变服务器。默认的接收字符集为GBK。
-- 修改服务器认为的客户端数据的字符集为GBK
set character_set_client=gbk;
插入中文的效果
查看数据效果:依然是乱码
原因:数据来源是服务器,解析数据是客户端(客户端只识别GBK:只会两个字节一个汉字),但是服务器给的数据是UTF8,所以导致乱码。
解决方案:修改服务器给客户端的数据字符集为GBK。
set character_set_results=gbk;
查看数据效果
set 变量=值;这样修改只是会话级别(当前客户端当次连接有效,关闭失效)
设置服务器对客户端的字符集的认知,如果按照上面的方式,太麻烦了。可以使用快捷方式。
set names 字符集;
1.13 校对集问题
校对集:数据比较的方式。
校对集有三种方式:
_bin:binary,二进制比较,取出二进制位,一位一位的比较。区分大小写。
_cs:case sensitive,大小写敏感,区分大小写。
_ci:case insensitive.大小写不敏感,不区分大小写。
查看数据库所支持的绞对集
show collation;
校对集应用:只有当数据产生比较的时候,校对集才会生效。
1.14 Web乱码问题
动态web由三个部分构成:浏览器、web服务器(如Tomcat等)、数据库服务器,三个部分都有自己的字符集(尤其是中文),数据需要在三个部分之间来回传递,很容易产生乱码。
如果解决乱码问题:统一编码(三码合一)。
【第二章】
2.1 数据类型(列类型)
所谓的数据类型:对数据进行统一的分类,从系统的角度出发是为了能够使用统一的方式进行管理,更好的利用有限的空间。
SQL中将数据类型分成了三大类:
2.2 数值类型
数值类型数据:都是数值。
系统将数值型分为整数类型和浮点数类型。
2.2.1 整数类型
在SQL中因为更多的要考虑如何节省磁盘空间,所以系统将整数类型又细分成了5类:
tinyint 迷你整型,使用一个字节存储,表示的状态最多为256种。
smallint 小整型,使用2个字节存储,表示的状态最多为65536种。
mediumint 中整型,使用3个字节存储。
int 标准整型,使用4个字节存储。
bigint 大整型,使用8个字节存储。
创建一张整型表
create table my_int(
int_1 tinyint,
int_2 smallint,
int_3 mediumint,
int_4 int,
int_5 bigint
)charset utf8;
插入数据
-- 插入数据
insert into my_int(int_1,int_2,int_3,int_4,int_5) values(1,2,3,4,5);
查看数据
select * from my_int;
SQL中的数值类型全部都是默认有符号的:分正负。
有的时候,需要使用无符号数据,需要给数据类型限定为int unsigned;--无符号
给my_int表增加一个无符号类型。
alter table my_int add int_6 int unsigned;
插入无符号数据
insert into my_int (int_6) values (0);
如果插入的是负数
查看表结构的时候,发现每个字段的数据类型之后都会自带一个括号,里面有指定的数字
显示宽度:没有特别的含义,只是默认的告诉用户可以显示的形式而已,实际上用户是可以控制显示宽度,但是这种控制不会改变数据本身的大小。
增加一个int_7 int(1) 的字段
alter table my_int add int_7 int(1) unsigned ;
插入数据
显示宽度的意义:在于当数据不够显示宽度的时候,会自动让数据变成对应的显示宽度,通常需要搭配一个前导0(zerofill:零填充,零填充会导致数值变成无符号)来增加宽度,不改变值大小。
alter table my_int add int_8 int(2) zerofill;
零填充+显示宽度的效果
零填充的意义:保证数据格式。
2.3 小数类型
小数型:带有小数点或者范围超出整数类型的数值类型。
SQL中:将小数类型细分为两种:浮点型和定点型。
浮点型:小数点浮动,精度有限,会丢失精度。
定点型:小数点固定,精度固定,不会丢失精度。
2.3.1 浮点型
浮点型数据会因为超出范围之后,丢失精度(自动四舍五入)。
浮点型:理论上有两种精度。
float:单精度,占用4个字节存储数据,精度范围大概在7位左右。
double:双精度,占用8个字节存储和数据,精度范围大概在15位左右。
创建浮点数表:浮点的使用方式,直接float表示没有小数部分;float(M,D):M代表总长度,D代表小数部分长度,整数部分长度为M-D。
-- 浮点数表
create table my_float(
f1 float,
f2 float(10,2),--10位在精度范围之外
f3 float(6,2)--6位在精度范围之内
);
插入数据:可以直接小数,也可以科学计数法
2.3.2 定点型
定点型:绝对的保证整数部分不会被四舍五入(不会丢失精度),小数部分有可能。
2.4 时间日期类型
2.5 字符串类型
在SQL中,将字符串类型分成了6类:char,varchar,text,blob,enum和set。
定长字符串
char:磁盘(二维表)在定义结构的时候,就已经确定了最终数据的存储长度。
char(L):L代表length,可以存储的长度,单位为字符。最大长度值可以为255。char(4):在utf8环境下,需要4 * 3 = 12 个字节。
变长字符串
varchar:在分配空间的时候,按照最大的空间分配,但是实际上最终用了多少,是根据具体的数据来确定。
varchar(L):L代表length,理论长度是65536个字符,但是会出处1到2个字节来确定存储的实际长度。varchar(10):在utf8环境下,存储10个汉字,需要10 * 3 + 1 = 31 字节。
注意的是,在实际开发中,如果字符的长度超过255个,我们会使用text或blob来代替varchar。
如何选择定长或变长字符串?
定长的磁盘空间比较浪费,但是效率高;换言之,如果数据基本上确定长度都一样,就使用定长,如身份证号码,电话号码,手机号码等。
变长的磁盘空间比较节省,但是效率低;换言之,如果数据不能确定长度(不同的数据,长度有变化),如姓名,地址等。
文本字符串
如果数据量非常大,通常超过255个字符,就会使用文本字符串。
文本字符串根据存储的格式进行分类:text和blob。
text:存储文字。
blob:存储二进制数据(通常不用)。
枚举字符串
枚举:enum,实现将所有可能出现的结果都设计好,实际上存储的数据必须是规定好的数据中的一个。
枚举的使用方式:
定义:enum(可能出现的元素列表);
如:enum('男','女','不男不女','妖')。
使用:存储数据,只能存储上面定义好的数据。
集合字符串
集合和枚举很类似:实际存储的是数值,而不是字符串。
集合的使用元素:
使用:set(元素列表);
使用:可以使用元素列表中的元素(多个),使用逗号分隔。
2.6 MySQL记录长度
MySQL中规定:任何一条记录最长不能超过65535个字节。(varchar永远达不到理论值)
2.7 列属性
列属性:真正约束字段的是数据类型,但是数据类型的约束很单一。需要有一些额外的约束,来保证数据的合法性。
列属性有很多:null/not null;default;primary key;unique key,auto_increment,comment等
2.7.1 空属性
两个值:null(默认的)和not null(不为空)
数据库基本上字段默认为null,但是在实际开发的时候,尽可能的要保证所有的数据不应该为null,因为①空数据没有意义,②空数据没有办法参与运算。
2.7.2 描述属性
列描述:comment,描述:没有实际含义,是专门用来描述字段的,会根据表创建语句保存。
2.7.3 默认值
默认值: 某一种数据会经常性的出现某个具体的值,可以在一开始就指定好,在需要真实数据的时候,用户可以选择性的使用默认值。
用法:default 默认值;
【第三章】
3.1 字段属性
主键、唯一键和自增长。
3.1.1 主键
主键:primary key,一张表中只能有一个字段可以使用对应的键,用来唯一的约束该字段里面的数据,不能重复。
一张表只能有最多一个主键。
3.1.1.1 增加主键
在SQL操作中欧有多种方式可以给表增加主键,大体分为三种:
方案1:在创建表的时候,直接在字段之后,跟primary key关键字(主键本身不能为空)。
-- 增加主键
create table my_pri(
id int primary key,
name varchar(20) not null comment '姓名'
)charset utf8;
优点:非常直接;缺点:只能使用一个字段作为主键。
方案2:在创建表的时候,在所有的字段之后,使用primary key(主键字段列表)来创建主键,如果有多个字段作为主键,可以是复合主键。
create table my_pri2(
number char(10) comment '学号',
course char(10) comment '课程代码:3901+0000',
score tinyint unsigned default 60 comment '成绩',
-- 增加主键限制:学号和课程代码应该是唯一的
primary key (number,course)
)charset utf8;
方案3:当表已经创建好之后,额外追加主键:可以通过修改表字段属性,也可以直接追加。
alter table 表名 add primary key (字段列表);
前提:表中字段对应的数据本身是独立的(不重复)。
3.1.1.2 主键约束
主键对应的字段中的数据不允许重复,一旦重复,数据操作失败(增和该)。
3.1.1.3 主键更新 & 删除主键
没有办法更新主键:主键必须先删除,才能增加。
drop table 表名 drop primary key;
3.1.1.4 主键分类
在实际创建表的过程中,很少使用真实业务数据作为主键字段(业务主键,如学号、课程号)。
大部分的时候,是使用逻辑性的字段(字段没有业务含义,值是什么都没有关系),将这种字段主键称为逻辑主键。
3.1.2 自动增长
自增长:当对应的字段,不给值,或者给默认值,或者给null的时候,会自动的被系统触发,系统会从当前字段中的已有的最大值+1操作,得到一个新的不同的字段。
通常自动增长和主键搭配。
3.1.2.1 新增自增长
自增长特点:auto_increment
任何一个字段要做自增长必须前提是本身是一个索引(key一栏有值)。
自增长必须是数字。
一张表最多只能有一个自增长。
3.1.2.2 自增长使用
当自增长被给定的值为null或者默认值的时候,会触发自动增长。
3.1.2.3 修改自增长
自增长如果是涉及到字段改变:必须先删除自增长,后增加(一张薄只能有一个自增长)。
修改当前自增长已经存在的值:修改只能比当前已有的自增长的最大值大,不能小(小不生效)。
-- 修改表选项的值
alter table 表名 auto_increment = 值;
思考:为什么自增长是从1开始?为什么每次都是自增1呢?
所有系统的变现(如字符集、校对集)都是系统内部的变量进行控制的。
查看自增长对应的变量:show variables like 'auto_increment%';
可以修改变量实现不同的效果,但是修改是针对整个数据库的修改,而不是单张表,不建议修改。
-- 不建议修改
set auto_increment_increment = 5;
3.1.2.4 删除自增长
自增长是字段的一个属性:可以通过modify来进行修改(保证字段没有auto_increment即可)
alter table 表名 modify 字段 类型;
3.1.3 唯一键
一张表往往有很多字段需要具有唯一性,数据不能重复;但是一张表中只能有一个主键,所以唯一键就可以解决表中有多个字段需要唯一性的约束。
唯一键的本质和主键差不多,唯一键默认的允许自动为空,而且可以多个为空。
3.1.3.1 增加唯一键
基本上和主键差不多:三种方案。
方案一:在创建表的时候,字段之后直接跟unique/unque key。
方案二:在所有的字段之后增加unique key(字段列表);--复合唯一键
方案三:在创建表之后增加唯一键。
3.1.3.2 唯一键约束
唯一键与主键本质相同,唯一的区别就是唯一键默认允许为控控,而且是多个为空。
如果唯一键也不允许为空,那么与主键的约束作用是一致的。
3.1.3.3 更新唯一键&删除唯一键
更新唯一键:先删除唯一键,再增加唯一键。
删除唯一键,默认使用字段名作为索引名字
alter table 表名 drop index 索引名字;
3.2 索引
几乎所有的索引都是建立在字段之上。
系统根据某种算法,将已有的数据(未来可能新增的数据),单独建立一个文件:文件能够实现快速的匹配数据,并且能够快速的找到对应表中的记录。
索引的意义:
提升查询数据的效率。
约束数据的有效性(唯一性等)。
增加索引的前提条件:索引本身会产生索引文件(有时候可能比数据文件还打),会非常消耗磁盘空间。
如果某个字段需要作为查询条件经常使用,那么可以使用索引。
如果某个字段需要进行数据的有效性约束,也可以使用索引(主键、唯一键)。
MySQL中提供了多种索引。
主键索引 primary key
唯一索引 unique key
全文索引 fulltext index
普通索引 index
全文索引:针对文章内部的关键字进行索引。
全文索引最大的问题在于如何确定关键字。
3.3 关系
将实体与实体的关系,反应到最终数据库表的设计上来。将关系分成三种:一对一,一对多和多对多。
3.3.1 一对一
一对一:一张表的一条记录一定只能和另一张表的一条记录进行对应;反之亦然。
学生表:姓名、性别、年龄、身高、体重、婚姻、籍贯、家庭住址、紧急联系人。
id
姓名
性别
年龄
身高
体重
籍贯
家庭住址
紧急联系人
婚姻
表设计成以上这种形式,是符合要求的。其中姓名、性别、年龄、身高、体重是常用数据,但是婚姻、籍贯、住址和紧急联系人属于不常用数据。如果每次查询都是查询所有数据,不常用的数据就会影响效率,实际又不用。
解决方案:将常用的和不常用的信息分离存储,分成两张表。
学生常用信息表
id
姓名
性别
年龄
身高
体重
学生不常用信息表
籍贯
家庭住址
紧急联系人
婚姻
但是如果我有时候又需要使用不常用信息怎么办?
为了保证不常用信息和常用信息一定能够对应上,我们唯有找到一个具有唯一性的字段来共同连接两张表。--主键字段
所以,学习不常用信息表修改如下
id
婚姻
籍贯
家庭住址
紧急联系人
综上所述,一个常用表中的一条记录,永远只能在一张不常用表中匹配一条记录;反过来,一个不常用表中的一条记录在常用表中也只能匹配一条记录。
3.3.2 一对多
一对多:一张表中的一条记录可以对应另外一张表中的多条记录,反过来,另一张表的一条记录只能对应第一张表的一条记录,这种关系就是一对多或多对一。
学生和班级的关系:
学生表
id
姓名
年龄
性别
班级表
id
班级名字
我们知道,一个学生只能属于一个班级,而一个班级却有多个学生,一对多。
但是以上设计:解决了实体的设计表问题,但是没有解决关系问题:学生找不到班级,班级没有学生。
解决方案:在某一张表中增加一个字段,能够找到另一张表中的记录。如何做到呢?在学生表中增加一个字段指向班级表,因为学生表的记录只能匹配到一个班级的记录。
学生表
id
姓名
性别
年龄
班级id
班级主键
班级主键
3.3.3 多对多
多对多:一张表(A)的一条记录能够对应另外一张表(B)的多条记录;同时B表中的一条记录也能对应A表中的多条记录。
老师教学:;老师和学生
老师表
t_id
姓名
性别
工资
1
A
男
6000
2
B
女
8000
学生表
s_id
姓名
性别
分数
1
张三
男
59
2
李四
男
95
以上设计方案:实现了实体的设计,但是没有维护实体的关系。
一个老师教过多个学生,一个学生也被多个老师教多。
解决方案:不管在那张表中增加字段,都会出现问题:该字段要保存多个数据,而且是与其它表有关系的字段,不符合表的设计规范,增加一张新表,专门维护两张表之间的关系。
中间关系表:老师与学生的关系
T_ID
S_ID
1
1
1
2
2
1
2
2
增加了中间表之后:中间表与老师表形成了一对多的关系,而且中间表是多表,维护了能够唯一找到一表的关系。
3.4 范式
范式:是离散数学中的知识,是为了解决一种数据的存储与优化的问题(保存数据的存储之后,凡是能够通过关系寻找出来的数据,坚决不再重复存储,起终极目标是为了减少数据冗余)。
范式:是一种分层结构的规范,分为6层:每一层都比上一层更加严格。
六层范式:1NF、2NF、3NF、4NF、5NF和6NF,其中1NF要求最低,6NF要求最高。
MySQL属于关系型数据库:有空间浪费,而范式致力于节省存储空间。所以,在设计数据库的时候,会利用范式来指导设计。但是数据库不单是解决空间问题,还要保证效率;而范式只为解决空间问题,所以数据库的设计不可能完全按照范式的要求实现,所以一般情况下,只有前三种范式需要满足。
范式在数据库的设计当中是有指导意义,但是不是强制规范。
3.4.1 第一范式 1NF
第一范式:在设计表存储数据的时候,如果表中设计的字段存储的数据,在取出来使用之前还需要额外的处理(拆分),那么就说表的设计不满足第一范式。
第一范式:属性不可再分,字段保证原子性。
讲师代课表
讲师
性别
班级
教室
代课时间
代课时间(开始时间、结束时间)
朱元璋
男
java001班
B23
30天
2014-02-17 2014-05-05
朱元璋
男
java002班
C15
30天
2014-05-05 2014-05-30
李世民
男
Linux003班
C15
15天
2016-02-21 2014-06-20
上表的设计不存在问题,但是如果需求是将数据查出来之后,要求一个老师从什么时候开始上课,到什么时候结束课程,此时需要将代课时间进行拆分,不符合第一范式,因为数据不具有原子性,可以再拆分。
解决方案::将代课时间拆成两个字段就可以了。
3.4.2 第二范式 2NF
第二范式:在数据表设计的过程中,如果有复合主键,且表中有字段并不是由整个主键来确定,而是依赖主键的某个字段(主键的部分),存在字段依赖主键部分的问题,称之为部分依赖。第二范式就是要解决表设计不允许出现部分依赖。
姓名
性别
班级
教室
代课时间
开始时间
结束时间
朱元璋
男
java001班
C01
30天
2014-02-27
2014-05-05
朱元璋
男
java002班
B23
30天
2014-03-21
2014-05-30
李世民
男
Linux003班
A15
15天
2014-06-01
2014-06-20
在上面的表中:因为讲师没有办法作为独立主键,需要结合班级才能作为主键(复合主键:一个老师在一个班永远只带一个阶段的课)。代课时间、开始时间和结束时间字段都与当前的代课主键(讲师和班级):但是性别并不依赖班级,教室不依赖讲师,性别只依赖讲师,教室只依赖班级,出现了性别和教室依赖主键的一部分:即部分依赖。
解决方案:取消复合主键,使用逻辑主键。
3.4.3 第三范式 3NF
要满足第三范式,必须满足第二范式。
第三范式:理论上讲,一张表中的所有字段都应该直接依赖主键(逻辑主键除外),如果表设计中存在一个字段,并不直接依赖主键,而是通过某个非主键依赖,最终实现依赖主键,把这种不是直接依赖主键,而是依赖非初见字段的依赖关系称之为传递依赖。第三范式就是解决传递依赖的问题。
讲师代课表
id
讲师
性别
班级
教室
代课时间
开始时间
结束时间
1
朱元璋
男
java01班
A03
30天
2014-02-27
2014-05-05
2
朱元璋
男
Linux02班
B23
30天
2014-03-21
2014-05-30
3
李世民
男
java001班
A03
30天
2014-06-01
2014-06-20
以上设计方案中,性别依赖讲师存在,讲师依赖主键;教室依赖班级,班级依赖主键;性别和教室都存在传递依赖。
解决方案:将存在传递依赖的字段,一级依赖的字段本身单独取出,形成一个单独的表,然后在需要对应的信息的时候,使用对应的实体表的主键加起来。
id
讲师id
班级id
代课时间
开始时间
结束时间
1
1
10
30天
2014-02-27
2014-05-05
2
1
12
30天
2014-03-21
2014-05-30
3
2
12
30天
2014-06-01
2014-06-20
id
讲师
性别
1
朱元璋
男
2
李世民
男
id
班级
教室
10
java01班
A03
12
Linux02班
B23
3.5 逆规范化
有的时候,在设计表的时候,如果一张表中有几个字段是需要从另外的表中去获取信息。理论上讲,的确可以获取到想要的数据,但是就是效率低一点。所以我们会刻意在某些表中,不去保存另外表的主键(逻辑主键),而是直接保存想要的数据信息,这样一来,在查询数据的时候,一张表可以直接提供数据,而不需要多表查询(效率低),但是会导致数据冗余增加。
【第四章】
4.1 蠕虫复制
蠕虫复制:从已有的数据中去获取数据,然后将数据又进行新增操作,数据成倍增加。
表创建高级操作:从已有创建新表(复制表结构)
create table 表名 like 数据库.表名;
蠕虫复制:先查出数据,然后将查出的数据新增一遍。
insert into 表名 [(字段列表)] select 字段列表/* from 数据表名;
蠕虫复制的意义
从已有表拷贝数据到新表中
可以迅速的让表中的数据膨胀到一定的数量级:测试表的压力以及效率。
4.2 查询
基本语法
select 字段列表/* from 表名 [where 条件];
完整语法
select [select 选项] 字段列表 [字段别名]/* from 数据源 [where 条件子句] [group by 子句] [having 子句][order by子句][limit 子句];
select选项:select对查出来的结果的处理方式。
all :默认的,保留所有的结果。
distinct:去重,查出来的结果,将重复给去除(所有字段都相同)。
字段别名
当数据进行查询出来的时候,有时间名字并不一定就满足需求(多表查询的是偶,会有同名字段),需要对字段进行重命名。
字段 [as] 别名;
数据源
数据源:数据的来源,关系型数据库的来源都是数据表,本质上只要保证数据类似二维表,最终都可以作为数据源。
数据源分为:
单表数据源
select * from 表名;
多表数据源(笛卡尔积)
select * from 表名1,表名2,……;
where子句
用来判断数据,筛选数据。
where子句返回结果:0(代表false)或1(代表true)。
判断条件:
比较运算符:>、=、<=、!=、<>、like、between and、in、not in
逻辑运算符:and、or、not
where原理:where是唯一一个直接从磁盘获取数据的时候就开始判断的条件,从磁盘取出一条记录,开始进行where判断,判断的结果如果成立就保存到内存,如果失败直接放弃。
group by子句
group by:分组,根据某个字段进行分组(相同的放一组,不同的分到不同的组)
分组的意义:是为了统计数据(按组统计:按分组字段进行统计)。
having子句
having子句:where是针对磁盘数据进行判断,进入到内存之后,会进行分组操作,分组结果就需要having来处理。
having能够使用字段别名:where不能,where是从磁盘
order by子句
根据某个字段进行升序或降序排序,依赖校对集。
语法:
order by 字段名 [asc|desc];
limit子句
limit子句是一种限制结果的语句:限制数量。
两种方式:
只用来显示长度:limit 数据量;
分页:limit m,n;m--起始页码 n--每页显示条数。
【第五章】
5.1 连接查询简介
将多张表(可以大于2)进行记录的连接(按照某个指定的条件进行数据拼接)。
最终结果:记录数可能会有变化,字段书一定会增加(至少两张表的合并)。
连接查询:join,使用方式:左表 join 右表
左表:join关键字左边的表
右表:join关键字右边的表
连接查询的意义:在用户查看数据的时候,需要显示的数据来自多张表。
连接查询的分类:
交叉连接
内连接
外连接
自然连接
5.2 交叉连接
交叉连接:cross join,从一张表中循环取出每一条记录,每条记录都去另外一张表进行匹配,无条件匹配,而连接本身字段就会增加,最终形成的结果叫做笛卡尔积。
基本语法:左表 cross join右表;====from 左表,右表。
select * from 左表,右表;
select * from 左表 cross join 右表;
my_class表
CREATE TABLE my_class(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
room VARCHAR(20)
);
my_student表
CREATE TABLE my_student(
id INT PRIMARY KEY AUTO_INCREMENT,
number VARCHAR(20),
sex CHAR(1),
NAME VARCHAR(10),
age INT,
height INT,
cid INT,
CONSTRAINT fk_id FOREIGN KEY (cid) REFERENCES my_class(id)
);
插入数据
-- 插入班级
INSERT INTO my_class VALUES (NULL,'java001班','A05');
INSERT INTO my_class VALUES (NULL,'Linux班','B26');
INSERT INTO my_class VALUES (NULL,'C班','D11');
-- 插入学生
INSERT INTO my_student VALUES (NULL,'haha002','男','张三',20,185,1);
INSERT INTO my_student VALUES (NULL,'haha003','女','李四',15,175,2);
INSERT INTO my_student VALUES (NULL,'haha004','女','王五',35,180,3);
INSERT INTO my_student VALUES (NULL,'haha005','男','赵六',19,181,3);
INSERT INTO my_student VALUES (NULL,'haha006','女','田七',34,164,2);
INSERT INTO my_student VALUES (NULL,'haha007','女','王八',11,1519,1);
交叉连接
SELECT * FROM my_student CROSS JOIN my_class;
笛卡尔积没有意义:应该尽量避免(交叉连接没有用)。
交叉连接存在的价值:保证连接这种结构的完整性。
5.3 内连接
内连接:[inner] join,从左表中取出每一条记录,去右表中与所有的记录进行匹配,匹配必须在某个条件在左表中和右表中相同最终才会保留结果,否则不保留。
基本语法:
select * from 左表 [inner] join 右表 on 左表.字段 = 右表.字段;
on 表示连接条件:条件字段都是代表相同的业务含义(如my_student.cid和my_class.id)
内连接
SELECT * FROM my_student INNER JOIN my_class ON my_student.`cid` = my_class.`id`;
字段别名以及表别名的使用:在查询数据的时候,不同表有同名字段,这个时候需要加上表名才能区分,而表名太长,通常可以使用别名。
SELECT c.`id` 班级id,c.`name` 班级名字,c.`room` 班级教室,s.`id` 学生id,s.`name` 学生姓名,s.`sex` 学生性别,s.`number` 学号,s.`age` 学生年龄,s.`height` 学生身高 FROM my_student s INNER JOIN my_class c ON s.`cid` = c.`id`;
设置id=5的cid=null
update my_student set cid = null where id = 5;
SELECT c.`id` 班级id,c.`name` 班级名字,c.`room` 班级教室,s.`id` 学生id,s.`name` 学生姓名,s.`sex` 学生性别,s.`number` 学号,s.`age` 学生年龄,s.`height` 学生身高 FROM my_student s INNER JOIN my_class c ON s.`cid` = c.`id`;
内连接还可以使用where代替on关键字(where没有on的效率高)
SELECT c.`id` 班级id,c.`name` 班级名字,c.`room` 班级教室,s.`id` 学生id,s.`name` 学生姓名,s.`sex` 学生性别,s.`number` 学号,s.`age` 学生年龄,s.`height` 学生身高 FROM my_student s INNER JOIN my_class c WHERE s.`cid` = c.`id`;
5.4 外连接
外连接:outer join,以某张表为主,取出里面的所有记录,然后每条记录去另外一张表进行连接,不管能不能匹配上条件,最终都会保留,能匹配,正确保留,不能匹配,其他表的字段都置空。
外连接分为两种:是以某张表位置:有主表
left outer join:左外连接,以左表为主表。
right outer join:右外连接,以右表为主表。
基本方法:左表 left/right join 右表 on 左表.字段 = 右表.字段;
左外连接--左表为主表:最终记录数至少不小于左表已有的记录数
SELECT s.*,c.name ,c.room FROM my_student s LEFT OUTER JOIN my_class c ON s.`cid` = c.`id`;
右外连接--与左外连接,相反。
5.5 自然连接
自然连接:natural join,自然连接,就是自动匹配连接条件:系统以字段名字作为匹配模式(同名字段就作为条件,多个同名字段都作为条件)
自然连接:自然内连接和自然外链接。
自然内连接:自动使用同名字段作为连接条件,连接之后会合并同名字段
SELECT * FROM my_student NATURAL JOIN my_class;
自然外连接:左表 natural left/right join 右表;
SELECT * FROM my_student NATURAL LEFT JOIN my_class;
5.6 温馨小提示
平常中,我们使用最多的还是内连接和外链接哦,交叉连接和自然连接没什么卵用。
【第六章】
6.1 外键
外键:foreign key,外面的键(键不在自己表中),如果一张表中有一个字段(非主键)指向另外一张表的主键,那么将该字段称为外键。
6.1.1 增加外键
外键可以在创建表的时候或创建表之后增加(但是要考虑数据的问题)。
方案一:在创建表的时候增加外键,在所有的表字段之后,使用foreign key(外键字段) references 外部表 (主键字段);
-- 创建班级
CREATE TABLE my_class(
id INT PRIMARY KEY AUTO_INCREMENT,
c_name VARCHAR(20) NOT NULL,
room VARCHAR(20)
);
-- 创建学生表
CREATE TABLE my_student1(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
c_id INT ,
CONSTRAINT fk_c_id FOREIGN KEY (c_id) REFERENCES my_class (id)
);
方案二:在新增表之后,增加外键,所以需要修改表结构。
alter table 表名 add [constraint 外键名字] foreign key (外键字段) references 父表(主键字段);
-- 创建班级
CREATE TABLE my_class(
id INT PRIMARY KEY AUTO_INCREMENT,
c_name VARCHAR(20) NOT NULL,
room VARCHAR(20)
);
-- 创建学生表
CREATE TABLE my_student2(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
c_id INT
);
ALTER TABLE my_student2 ADD CONSTRAINT fk_c_id FOREIGN KEY (c_id) REFERENCES my_class (id);
6.1.2 修改外键&删除外键
外键不可以修改:只能先删除后新增。
alter table 表名 drop foreign key 外键名;--一张表中可以有多个外键,但是名字不能相同
ALTER TABLE my_student2 DROP FOREIGN KEY fk_c_id;
6.1.3 外键作用
外键默认的作用有两点:
一个对父表:父表数据进行写操作(删和改,都必须设计到主键本身),如果对应的主键在字表中已经被数据所引用,那么就不允许操作。
一个对字表(外键字段所在的表):字表数据进行写操作(增和改)的时候,如果对应的外键字段在父表找不到对应的匹配,操作会失败。
6.1.4 外键条件
外键要存在:首先必须表的存储引擎是innodb。如果不是innodb存储引擎,那么外键可以创建成功,但是没有约束效果。
外键字段的字段类型(列类型)必须和父表的主键类型完全一致。
一张表中的外键名字不能重复。
增加外键的字段(数据已经存在),必须保证数据和父表主键要求对应。
6.1.5 外键约束
外键约束:就是外键的作用。
外键约束有三种约束模式:都是针对父表的约束
district:严格模式(默认的),父表不能删除或更新一个已经被子表数据引用的记录(主键)。
cascade:级联模式,父表的操作,对应子表关联的数据也随之变化。
set null:置空模式,父表的操作之后,子表对应的数据(外键)被置空。
通常:父表删除的时候,子表置空;更新的时候,子表级联操作。
constraint 外键名字 foreign key (外键字段) references 主表(主键) on delete set null;
constraint 外键名字 foreign key (外键字段) references 主表(主键) on update cascade;
constraint 外键名字 foreign key (外键字段) references 主表(主键) on delete set null on update cascade;
删除置空的前提:外键字段为空(如果不满足条件,外键无法创建)。
外键虽然很强大,能够进行各种约束,但是对于java来说,外键约束降低了java对数据的可控性。所以,通常情况下,在实际开发中,很少使用外键的级联模式和置空模式。
6.2 联合查询
联合查询:将多次查询语句,在记录上进行拼接。
基本语法
多条select语句构成:每一条select语句获取的字段必须严格一致(但是字段类型无关)
select 语句1 union [union 选项] 语句2……;
-- union选项:与select选项一样有两个
-- all 保留所有(不管重复)
-- distinct 去重(整个重复):默认的
示例:联合查询
SELECT * FROM my_class
UNION
SELECT * FROM my_class;
示例:联合查询
SELECT id,c_name,room FROM my_class
UNION
SELECT id,c_id,NAME FROM my_student2;
联合查询的意义
查询同一张表,但是需求不同:如查询学生信息,男生身高升序,女生身高降序(面试题)。
多表查询:多张表的结构是完全一样的,保存的数据(结构)也是一样的。
联合查询Order by使用
在联合查询中:order by不能直接使用,需要对查询语句使用括号才行。
(SELECT * FROM my_student2 WHERE sex = '男' ORDER BY height ASC)
UNION
(SELECT * FROM my_student2 WHERE sex = '女' ORDER BY height DESC);
如果想order by生效:必须搭配limit,limit使用限定的最大数即可。
(SELECT * FROM my_student2 WHERE sex = '男' ORDER BY height ASC LIMIT 999999999)
UNION
(SELECT * FROM my_student2 WHERE sex = '女' ORDER BY height DESC LIMIT 999999999);
6.3 子查询
子查询:查询是在某个查询结果之上进行的(一条select语句内部包含了另外一条select语句)。
子查询分类方式:
按位置分类:子查询(select语句)在外部查询(select语句)中出现的位置。
From子查询:子查询在From之后
where子查询:子查询出现在where条件中
exists子查询:子查询出现在exists里面
按照结果分类:根据子查询得到的数据进行分类(理论上任何一个查询得到的结果都可以理解为二维表)
标量子查询:子查询得到的结果是一行一列。(出现在where之后)
列子查询:子查询得到的结果是一列多行。(出现在where之后)
行子查询:子查询得到的结果是多列一行(多行多列)(出现在where之后)
表子查询:子查询得到的结果是多行多列(出现的位置在From之后)
示例脚本
-- 创建班级表
CREATE TABLE my_class(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
c_name VARCHAR(20) NOT NULL COMMENT '班级名字',
room VARCHAR(20) NOT NULL COMMENT '班级所在教室'
);
-- 插入班级信息
INSERT INTO my_class VALUES (NULL,'java001班','A01');
INSERT INTO my_class VALUES (NULL,'Linux003班','C15');
INSERT INTO my_class VALUES (NULL,'c005班','B23');
-- 创建学生表
CREATE TABLE my_student(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
NAME VARCHAR(20) NOT NULL COMMENT '学生姓名',
age INT NOT NULL COMMENT '学生年龄',
gender VARCHAR(2) NOT NULL COMMENT '学生性别',
c_id INT COMMENT '外键' ,
CONSTRAINT fk_c_id FOREIGN KEY (c_id) REFERENCES my_class (id)
);
-- 插入学生信息
INSERT INTO my_student VALUES (NULL,'张三',20,'男',1);
INSERT INTO my_student VALUES (NULL,'李四',18,'女',2);
INSERT INTO my_student VALUES (NULL,'王五',19,'女',2);
INSERT INTO my_student VALUES (NULL,'赵六',25,'男',3);
INSERT INTO my_student VALUES (NULL,'田七',14,'女',1);
INSERT INTO my_student VALUES (NULL,'王八',19,'男',3);
6.3.1 标量子查询
示例:查询java001班的所有学生。
①确定数据源:获取所有的学生
select * from my_student where c_id = ?;
②获取班级id:可以通过名字确定。
SELECT id FROM my_class WHERE c_name = 'java001班' ; -- 一行一列
③最后的执行SQL语句
SELECT * FROM my_student WHERE c_id = (SELECT id FROM my_class WHERE c_name = 'java001班' );
6.3.2 列子查询
示例:查询所有在读班级的学生(班级表中存在的班级)
①确定数据源:学生
SELECT *FROM my_student WHERE c_id IN (?);
②确定有效班级的id:所有班级的id
SELECT id FROM my_class
最后的执行SQL语句
SELECT *FROM my_student WHERE c_id IN (SELECT id FROM my_class);
列子查询返回的结果会比较:一列多行,需要使用in作为条件匹配:其实在mysql中还有几个类似的条件:all、some、any。
6.3.3 行子查询
行子查询:返回的结果可以是多行多列(一行多列)
修改学生表
ALTER TABLE my_student ADD height INT NOT NULL COMMENT '身高';
UPDATE my_student SET height = 180 WHERE id = 1;
UPDATE my_student SET height = 170 WHERE id = 2;
UPDATE my_student SET height = 165 WHERE id = 3;
UPDATE my_student SET height = 190 WHERE id = 4;
UPDATE my_student SET height = 155 WHERE id = 5;
UPDATE my_student SET height = 160 WHERE id = 6;
示例:要求查询整个学生中,年龄最大且身高
思路1:
①确定数据源
select * from my_student where age = ? and height = ?;
②确定最大的年龄和最高的身高
select max(age),max(height) from my_student;
③最后的SQL执行语句
SELECT * FROM my_student WHERE (age = (SELECT MAX(age) FROM my_student)) AND (height = (SELECT MAX(height) FROM my_student));
④貌似上面的是对的哦,但是如果最高的年龄最大,身高最高的不是一个人呢?这样就不能查到数据呢,所以,综上所述,上面的思路貌似正确,但是不合题意。
思路2:正确解法
需要构造行元素:行元素是由多个字段构成。
SELECT * FROM my_student WHERE (age,height) = (SELECT MAX(age),MAX(height) FROM my_student);
6.3.4 表子查询
表子查询:子查询返回的结果是当做二维表来使用。
示例:找出每个班中最高的一个学生。
①先对学生按照身高降序
SELECT * FROM my_student ORDER BY height DESC
②对排序后的学生临时表按照班级分组
SELECT * FROM (SELECT * FROM my_student ORDER BY height DESC) AS student GROUP BY student.c_id;
当然,这一题可能有人会这样想?我先对学生分组,求出最大的年龄,然后用in子句,就可以了。
SELECT * FROM my_student WHERE height IN (SELECT MAX(height) FROM my_student GROUP BY c_id);
当然,第二种思路也是可以的。
6.3.5 exists子查询
exists:是否存在,exists子查询是用来判断某些条件是否满足(跨表),exists是在where之后,exists返回的结果是0或1。
示例:查询所有的学生,前提条件是班级存在。
①确定数据源
SELECT * FROM my_student WHERE ?;
②确定条件是否满足
EXISTS(SELECT * FROM my_class)
最后的执行SQL语句
SELECT * FROM my_student WHERE EXISTS(SELECT * FROM my_class);
【第七章】
7.1 视图
视图:View,是一种有结构(有行有列)但是没结果(结构中不真实存放的数据)的虚拟表,虚拟表的结构来源不是自己定义,而是从对应的基表中产生(视图的数据来源)。
示例脚本:
CREATE TABLE my_class(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
c_name VARCHAR(20) NOT NULL COMMENT '班级名字',
room VARCHAR(20) NOT NULL COMMENT '班级所在教室'
);
-- 插入班级信息
INSERT INTO my_class VALUES (NULL,'java001班','A01');
INSERT INTO my_class VALUES (NULL,'Linux003班','C15');
INSERT INTO my_class VALUES (NULL,'c005班','B23');
-- 创建学生表
CREATE TABLE my_student(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
NAME VARCHAR(20) NOT NULL COMMENT '学生姓名',
age INT NOT NULL COMMENT '学生年龄',
gender VARCHAR(2) NOT NULL COMMENT '学生性别',
c_id INT COMMENT '外键' ,
height INT COMMENT '身高',
CONSTRAINT fk_c_id FOREIGN KEY (c_id) REFERENCES my_class (id)
);
-- 插入学生信息
INSERT INTO my_student VALUES (NULL,'张三',20,'男',1,180);
INSERT INTO my_student VALUES (NULL,'李四',18,'女',2,170);
INSERT INTO my_student VALUES (NULL,'王五',19,'女',2,165);
INSERT INTO my_student VALUES (NULL,'赵六',25,'男',3,190);
INSERT INTO my_student VALUES (NULL,'田七',14,'女',1,155);
INSERT INTO my_student VALUES (NULL,'王八',19,'男',3,160);
INSERT INTO my_student VALUES (NULL,'陈九',26,'男',NULL,195);
7.1.1 创建视图
基本语法
create view 视图名字 as select 语句;
-- select 语句可以是普通查询;可以是连接查询;可以是联合查询;可以是子查询
创建单表视图:基表只有一个
CREATE VIEW v1 AS SELECT * FROM my_student;
CREATE VIEW v2 AS SELECT * FROM my_class;
创建多表视图:基表至少两个
CREATE VIEW v3 AS SELECT * FROM my_student AS s LEFT OUTER JOIN my_class AS c ON s.c_id = c.id ;
为什么?我们知道两张表都有id字段,而我们又知道的是视图是有结构但没结果的虚拟表,既然它是虚拟表,怎么可能一张表有两个相同的字段呢?
CREATE VIEW v3 AS SELECT s.*,c.c_name,c.room FROM my_student AS s LEFT OUTER JOIN my_class AS c ON s.c_id = c.id ;
7.1.2 查看视图
查看视图:查看视图的结构
视图是一张虚拟表,那么表的所有查看方式都适用于表。
show tables;
DESC v1;
SHOW CREATE TABLE v1;
视图比表还是有一个关键字的区别:View。查看视图的创建语句的时候可以使用View关键字。
视图一旦创建:系统会在视图对应的数据库文件夹下创建一个对应的结构文件:frm文件。
7.1.3 使用视图
使用视图主要为了查询,将视图当做表即可。
示例:查看v1,v2,v3视图
SELECT * FROM v1;
SELECT * FROM v2;
SELECT * FROM v3;
视图的执行:其实本质就是执行封装的select语句。
7.1.4 修改视图
视图本身不可修改,但是视图的来源是可以修改的。
修改视图就是修改视图本身的来源语句(select语句)。
基本语法:
alter view 视图名字 as 新的select语句;
ALTER VIEW v1 AS SELECT id,NAME,age,gender FROM my_student;
SELECT * FROM v1;
7.1.5 删除视图
基本语法
drop view 视图名字;
CREATE VIEW v4 AS SELECT * FROM my_student;
SHOW TABLES;
DROP VIEW v4;
SHOW TABLES;
7.1.6 视图的意义
① 视图可以节省SQL语句:将一条复杂的查询语句使用视图进行保存,以后可以直接对视图进行操作。
②数据安全:视图操作是主要针对查询的,如果对视图结构进行处理(删除),不会影响基表数据,相对安全。
③视图往往是在大项目中使用,而且是多系统使用:可以对外提供有用的数据,但是隐藏关键(对外来说无用)的数据,这样数据可以相对安全。
④视图可以对外提供友好性:不同的视图提供不同的数据,对外好像专门设计一样。
⑤视图可以更好(容易)的进行权限控制。
7.1.7 视图数据操作
视图的确可以进行数据写操作,但是是有限制的。
7.1.7.1 视图的新增数据
数据的新增就是直接对视图进行数据新增。
多表视图不能新增数据
INSERT INTO v3 VALUES (NULL,'呵呵',50,'女',1,180,'java002班','C05');
可以向单表视图插入数据:但是视图中包含的字段必须有基表中所有不能为空(或者没默认值)的字段。
-- 给学生增加学号
ALTER TABLE my_student ADD number VARCHAR(5) NOT NULL AFTER id ;
-- 修改学号
UPDATE my_student SET number = '001' WHERE id =1;
UPDATE my_student SET number = '002' WHERE id =2;
UPDATE my_student SET number = '003' WHERE id =3;
UPDATE my_student SET number = '004' WHERE id =4;
UPDATE my_student SET number = '005' WHERE id =5;
UPDATE my_student SET number = '006' WHERE id =6;
UPDATE my_student SET number = '007' WHERE id =7;
-- 单表视图插入:视图不包含所有不允许为空字段(学号)
INSERT INTO v1 VALUES (NULL,'张三丰',120,'男');
视图是可以插入数据的
SELECT * FROM v2;
SELECT * FROM my_class;
INSERT INTO v2 VALUES(NULL,'C++007班','D13');
SELECT * FROM v2;
SELECT * FROM my_class;
7.1.7.2 视图的删除数据
多表视图不能删除,原因:如果有一个视图能查询到学生和班级信息,那么如果我想删除一个学生的时候,却将班级删除了,于是,这个被删除的班级下的所以学生都没班级了,这就尴尬了。想象一下如下场景:学生转班,当然是先在原来的班级中删除此学生,然后在新的班级中增加此学生,而如果多表视图可以删除,岂不是学生不可以转班了,否则一旦转班,必须将自己原来的班级删除,很可怕的哦。
SELECT * FROM v3;
DELETE FROM v3 WHERE id = 7;
单表视图可以删除
DELETE FROM v2 WHERE id = 4;
SELECT * FROM v2;
7.1.7.3 视图的更新数据
理论上不但单表视图还是多表视图都可以更新数据
SELECT * FROM v3;
UPDATE v3 SET c_id = 1 WHERE id = 7;
SELECT * FROM v3;
更新限制:with check option,如果对视图在新增的时候,限定了某个字段有限制;那么在对视图进行数据更新的时候,系统会进行验证:要保证更新之后,数据依然可以被视图查询出来,否则不让更新。
-- 视图:age字段限制更新
CREATE VIEW v4 AS SELECT * FROM my_student WHERE age >20 WITH CHECK OPTION;
-- 表示视图的数据来源都是年龄大于20岁:where age > 20
-- -- with check option:决定通过视图进行数据更新的时候,不能将已经得到的数据 age > 20 改成小于20 的
SELECT * FROM v4;
UPDATE v4 SET age = 18 WHERE id = 4; --将视图可以查询到的改成小于20
为什么会报错呢?因为你在修改视图可以查询到的,如果你改成小于20,那么视图还可以查询到吗?显然不能,所以,综上所述,如果能保证视图查询到的数据不变,否则,系统不会让你修改。当然,如果你改了一些视图查询不到的数据,那么当然可以了,反正视图查询不到。
UPDATE v4 SET age = 30 WHERE id = 1;
当然,你可以改了,但是没有效果,因为在视图中id=1是不存在的,只有id=4和id=7的存在。
7.1.7.4 视图算法
视图算法:系统对视图以及外部查询视图的select语句的一种解析方式。
视图算法分为三种:
①undefined:未定义,这不是一种实际使用的算法,是一种推卸责任的算法,告诉系统,视图没有定义算法,系统自己看着办。
②temptable:临时表算法,系统应该先执行视图的select语句,后执行外部查询语句(此种方式效率低,因为相对合并算法,至少需要查询两次)。
③merge:合并算法,系统应该先将视图对应的select语句与外部查询视图的select语句进行合并,然后执行(此种方式,效率高,因为只查询一次)
示例:没有指定视图算法,查询每个班身高最高的那个学生。
SELECT * FROM my_student;
-- 不使用视图
SELECT * FROM (SELECT * FROM my_student m ORDER BY m.`height` DESC) temp GROUP BY temp.c_id;
-- 不使用视图
SELECT * FROM my_student WHERE height IN ( SELECT MAX(height) FROM my_student m GROUP BY m.`c_id` );
-- 使用视图
CREATE VIEW v5 AS SELECT * FROM my_student m ORDER BY m.height DESC;
SELECT * FROM v5 GROUP BY c_id;
看吧,查询结果是不真确的,为什么,是因为视图算法是undefined。
所以,指定视图算法吧
create algorithm=指定算法 view 视图名字 as select语句;
-- 使用视图
CREATE ALGORITHM=TEMPTABLE VIEW v5 AS SELECT * FROM my_student m ORDER BY m.height DESC;
SELECT * FROM v5 GROUP BY c_id;
视图算法选择:如果视图的select语句会包含一个查询子句(五子句),而且很有可能顺序比外部的查询要靠后,一定要使用算法temptable,其他情况可以不用指定(默认即可)。
7.2 数据备份与还原
备份:将当前已有的数据或者记录保留。
还原:将已经保留的数据恢复到对应的表中。
为什么要做备份还原?
①防止数据丢失:被盗、误操作。
②保护数据记录。
数据备份还原的方式有多种:数据表备份,单表数据备份,SQL备份,增量备份。
7.2.1 数据表备份
不需要通过SQL来备份:直接进入到数据库文件夹复制对应的表结构以及数据文件,以后还原的时候,直接将备份的内容放进去即可。
数据表备份有前提条件:根据不同的存储引擎有不同的区别。
存储引擎:MySQL进行数据存储的方式,主要有两种:innodb和myisam(免费)。
innodb:只有表结构,数据全部存储在ibdata1文件中。
myisam:表,数据和索引全都单独分开存储。
CREATE TABLE my_isam(
id INT
)CHARSET utf8 ENGINE = MYISAM;
这种文件复制非常适合myisam存储引擎:直接复制这三个文件即可,然后放到对应的数据库下就可以使用了。
7.2.2 单表数据备份
每次只能备份一张表,只能备份数据(表结构不能备份)。
通常的使用:将表中的数据进行导出到文件。
备份:从表中选出一部分数据保存到外部的文件中(outfile)。
select */字段 into outfile '文件所在路径' from 数据源; --前提外部文件不存在
SELECT * INTO OUTFILE 'e:/a.txt' FROM my_student;
数据还原:将一个在外部保存的数据重新恢复到表中(如果表结构不存在,还原不了)
LOAD DATA INFILE '文件所在路径' INTO TABLE 表名;
LOAD DATA INFILE 'e:/a.txt' INTO TABLE my_student;
7.2.3 SQL备份
SQL备份:系统会对表结构以及数据进行处理,变成对应的SQL语句,然后进行备份。
还原:只要执行SQL语句即可。
备份:mysql没有提供备份指令,需要利用mysql提供的软件:mysqldump.exe。
mysqldump.exe也是一种客户端,需要操作服务器:必须连接认证。
mysqldump -hPUP 数据库名字 [数据表名字1 [数据表名字2]] > 外部文件目录(建议使用.sql结尾)
SQL备份
mysqldump -uroot -proot test > e:test.sql
还原:
使用mysql.exe客户端还原
mysql -uroot -proot 数据库名字 < 备份文件目录
使用SQL指令还原
source 备份文件所在路径
SQL备份优缺点:
①优点:
可以备份结构。
②缺点:
会浪费空间(额外的增加SQL指令)
7.2.4 增量备份
不是针对数据或者SQL指令进行备份:是针对mysql服务器的日志文件进行备份。
增量备份:指定时间段开始进行备份,备份数据不会重复,而且所有的操作都会备份。
【第八章】
8.1 事务
需求:有一张银行账户表,A用户给B用户转账,A账户先减少,B账户增加,但是A操作完之后断电了。
解决方案:A减少钱,但是不要立即修改数据表,B收到钱之后,同时修改数据表。
事务:一系列要发生的连续的操作。
事务安全:一种保护连续操作同时满足(实现)的机制,事务安全的意义:保证数据操作的完成性。
示例SQL脚本
-- 创建一个账户表
CREATE TABLE my_account(
id int primary key auto_increment,
number CHAR(16) NOT NULL COMMENT '账户',
NAME VARCHAR(20) NOT NULL COMMENT '姓名',
money DECIMAL(10,2) DEFAULT 0.0 COMMENT '账户余额'
);
-- 插入数据
INSERT INTO my_account VALUES (null,'0000000000000001','张三',1000);
INSERT INTO my_account VALUES (null,'0000000000000002','李四',2000);
-- 张三转账
UPDATE my_account SET money = money - 1000 WHERE NAME = '张三';
张三一转完钱,就退出了系统,然后,可怕的是,李四没有收到,那么张三就少了这1000元,很可怕,如果天下银行都这么办,估计银行早就倒闭了。那怎么办,那就是要进行事务操作。
8.1.1 事务操作
事务操作有两种:自动事务(默认的)和手动事务。
手动事务:操作流程
①开启事务(start transaction):告诉系统以下所有的操作(写)不要直接写入到数据库,先存放到事务日志。
②进行事务操作:一系列的操作(李四借钱给张三)。
李四账户减少
-- 事务操作:1李四账户减少1000
UPDATE my_account SET money = money -1000 WHERE NAME = '李四';
SELECT * FROM my_account;
我们在开启一个客户端,会发现如下的状况。
张三账户增加
-- 事务操纵:2张三账户增加1000
UPDATE my_account SET money = money +1000 WHERE NAME = '张三';
SELECT * FROM my_account;
我们如果再开启一个客户端,会发生如下状况
关闭事务:选择性的将事务日志文件中的操作结果保存到数据表(同步)或直接清空事务日志(原来的操作全部清空)。
提交事务(commit):同步数据表(操作成功)
-- 提交事务
COMMIT;
当提交完事务的时候,我们会发现再开启一个客户端,两边的数据是一样的。
回滚事务(rollback):直接清空日志表(操作失败)
8.1.2 事务原理
事务操作原理:事务开启之后,所有的操作都会临时保存到事务日志,事务日志只有在得到commit命令才会同步到数据库表,其它任何情况都会清空事务日志(rollback,断电,断开连接)。
8.1.3 回滚点
回滚点:在某个成功的操作完成之后,后续的操作有可能成功或失败,但是不管成功还是失败,前面的操作都已经成功;那么就可以在当前成功的位置,设置一个点,供后续失败操作返回到该位置,而不是返回所有操作,这个点就是回滚点。
设置回滚点:savepoint 回滚点名字。
回到回滚点:rollback to 回滚点名字。
-- 开启事务
START TRANSACTION;
-- 事务处理1:张三发工资
UPDATE my_account SET money = money + 10000 WHERE NAME = '张三';
-- 设置回滚点
SAVEPOINT sp1;
-- 银行扣税
UPDATE my_account SET money = money - 10000 * 0.5 WHERE NAME = '李四';
-- 回滚到回滚点
ROLLBACK TO sp1;
-- 继续给张三扣税
UPDATE my_account SET money = money - 10000 * 0.5 WHERE NAME = '张三';
-- 事务提交
COMMIT;
8.1.4 自动事务
在MySQL中:默认的都是自动事务处理,用户操作完全会立即同步到数据表中。
自动事务:系统通过autocommit的变量控制。
SHOW VARIABLES LIKE 'autocommit';
关闭自动事务:set autocommit = off;
SET autocommit = off;
再次开启自动事务:set autocommit = on;
8.1.5 事务的特定(ACID)
事务有四大特性:ACID
①A:atomic,原子性,事务的整体是一个操作,不可分割,要么全部成功,要么全部失败。
②C:consistency,一致性,事务操作的前后,事务表中的数据没有变化。
③I:isolation,隔离性,事务操作是相互隔离不受影响的。
④D:durability,持久性,数据一旦提交,不可该表,永久的改变数据表数据。
锁机制:innodb默认是行锁,但是如果在事务操作的过程中,没有使用到索引,那么系统会自动全表检索数据,自动升级为表锁。
行锁:只有当前行被锁住,别的用户不能操作。
表锁:整张表被锁住,别的用户不能操作。
8.2 变量
变量分为两种:系统变量和自定义变量。
8.2.1 系统变量
系统变量:系统定义好的变量,大部分的时候用户根本不需要使用系统变量,系统变量是用来控制服务器的表现的,如autocommit等。
查看系统变量
-- 查看所有系统变量
SHOW VARIABLES;
查看具体的系统变量的值:select @@系统变量名;
select @@version,@@autocommit;
修改系统变量
修改系统变量分为两种:会话级别和全局级别
会话级别:临时修改,当前客户端当次连接有效。
set 变量 = 值;
全局级别:一个修改,永久生效(多所有客户端都生效)
set global 变量名 = 值;
8.2.2 自定义变量
定义变量:
系统为了区别是系统变量还是自定义变量,规定自定义变量前面必须加@
set @变量 = 值;
查看自定义变量
slect @变量;
在MySQL中,"="会默认的当做比较符号处理(很多地方),MySQL为了区分比较还是赋值的概念,重新定义了一个新的赋值符号: :=。当然,这个符号一般在SQL编程中使用。
MySQL允许从数据表中获取数据,然后赋值给变量,有两种方式。
①边赋值,边查看结果
select @变量名 := 字段名 from 数据源;-- 从字段中取值赋给变量名
②只有赋值,没有结果,要求严格:数据记录最多只允许获取一条,MySQL不支持数组。
select 字段名 from 数据源 [where 条件] into 变量列表
SELECT NAME FROM my_account WHERE id = 1 INTO @name;
SELECT @name;
所有自定义变量都是会话级别:当前客户端当次连接有效。
8.3 触发器
触发器:trigger,事先为某张表绑定好一段代码,当表中的某些内容发生改变的时候(增、删、改),系统会自动触发代码,执行。
触犯器:事件类型、触发时间、触发对象。
事件类型:增、删、改。
触发时间:前后,before和after。
触发对象:表中的每一条记录,针对行的。
一张表中只能有一种触发时间的一种类型的触发器,最多一张表有6个触发器。
8.3.1 创建触发器
触发器基本语法:
-- 临时修改语句结束符
DELIMITER 自定义符号:后续代码中只有碰到自定义符号才算结束
CREATE TRIGGER 触发器名字 触发时间 事件类型 ON 表名 FOR EACH ROW
BEGIN -- 代表左大括号 开始
-- 里面就是触发器的内容:每行内容都必须使用;结束
END -- 代表右大括号 结束
-- 语句结束符
自定义符号
-- 将邻水修改修正过来
DELIMITER ;
示例脚本:
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
price DECIMAL(10,2) DEFAULT 1,
inv INT COMMENT '库存数量'
);
INSERT INTO goods VALUES (NULL,'iphone6',2680,100);
INSERT INTO goods VALUES (NULL,'iphone6s',2880,100);
CREATE TABLE `order`(
id INT PRIMARY KEY AUTO_INCREMENT,
g_id INT NOT NULL COMMENT '商品id',
g_number INT COMMENT '商品数量'
);
创建触发器
-- 触发器:订单生成一个,商品库存减少一个
-- 临时修改语句结束符
DELIMITER /
CREATE TRIGGER after_order AFTER INSERT ON `order` FOR EACH ROW
BEGIN
UPDATE goods SET inv = inv -1 WHERE id = 2;
END
-- 结束触发器
/
-- 修改临时语句结束符
DELIMITER ;
8.3.2 查看触发器
查看所有触发器或者模糊匹配
show triggers like 'pattern';
查看触发器创建语句
show create trigger after_order;
所有的触发器都会保存在information_schema.triggers表中。
8.3.3 使用触发器
触发器:不需要手动调用,当某种情况发生的时候会自动触发。
插入订单
SELECT * FROM goods;
INSERT INTO `order` VALUES (NULL,1,2);
SELECT * FROM goods;
①触犯器的确工作了:订单生成了后,对应的商品表的商品数量减少了。②当前商品减少了,并不是订单中产生的商品;而是固定死的商品(触发器不合适)。
8.3.4 修改触发器&删除触发器
触发器不能被修改,只能先删除,再新增新的触发器。
drop trigger 触发器名字;
8.3.5 触发器记录
触发器记录:不管触发器是否触发了,只要当某种操作准备执行,系统就会将当前要操作记录的当前状态和即将执行之后新的状态给分别保留下来,供触发器使用。其中,要操作当前状态保存到old中,操作之后的可能状态保存给new。
old代表的是旧记录,new代表的是新记录。
删除的时候是没有new的,而插入的时候是没有old的。
old和new都是代表记录本身:任何一条记录除了有数据,还有字段名称
使用方式:old.字段/new.字段。
DELIMITER $$
CREATE
TRIGGER `after_order` AFTER INSERT ON `order`
FOR EACH ROW BEGIN
UPDATE goods SET inv = inv -new.g_number WHERE id = new.g_id;
END;
$$
DELIMITER ;
SELECT * FROM goods;
INSERT INTO `order` VALUES (NULL,1,2);
如果触发器内部只有一条要执行的SQL指令,可以省略begin和end。
触发器:可以很好的协调表内部的数据处理顺序和关系,但是从java的角度出发,触发器会增加数据库维护的难度,所以较少使用触发器。
原载:http://www.cnblogs.com/xuweiweiailixing/