MySQL基础
数据库的概述
1. 为什么要使用数据库
- 持久化(persistence):==把数据保存到可掉掉电式存储设备中以供之后使用==,大多数情况下,特别是企业级应用,==数据库持久化意味着将内存中的数据保存到硬盘上加以“固化”==,而持久化的实现过程大多数通过各种关系数据库来完成
- 持久化的主要作用是==将内存中的数据存储在关系型数据库中==,当然页可以存储在磁盘文件、XML数据文件中。
2. 数据库的相关概念
==DB:数据库(Database)== |
---|
即存储数据的“仓库”,其本质是一个我呢见系统。它保存了一系列有组织的数据。 |
==DBMS:数据库管理系统(Database Management System)== |
是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控制。用户通过数据库管理系统访问数据库中表内的数据 |
SQL:结构化查询语言(Structured Query Language)==== |
专门用来与数据库通信的语言 |
3. RDBMS 和 非RDBMS
关系数据库绝对是DBMS的主流,其中使用最多的分别是Oracle、Mysql和SQL Server。这些都是关系型数据库。
3.1 关系型数据库(RDBMS)
3.1.1 实质
这种类型的数据库是最古老的数据库类型,关系型数据库模式是把复杂的数据结构归算为简单的二元关系(即二维表格形式)。
关系型数据库是以
行(row)
和列(column)
的形式存储数据,以便于用户理解。这一系列的行和列被称为表(table)
,一组表组成一个库(database).表与表之间的数据记录有关系(relationship)。现实世界中的各个实体以及实体之间的各种联系均用
关系模型
来
3.1.2 优势
复杂查询
可以使用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
事务支持
使得对于安全性能很高的数据访问要求得以实现。
3.2 非关系型数据库(非RDBMS)
3.2.1 介绍
非关系型数据库。可以看成传统关系型数据库的功能阉割版本
,基于键值存储数据,不需要经过SQL层的解析,性能非常高
。同时通过减少不常用的功能,进一步提供性能。
目前基本上大部分主流非关系型数据库都是免费的。
3.2.2 有哪些非关系型数据库
相比于SQL,NoSQL泛指非关系型数据库,包括榜单上的键值型数据库、文档型数据库,搜索引擎和列存储等,除此之外还包括图形数据库。也只有用NoSQL一词才能将这些技术囊括进来。
==键值型数据库==
键值型数据库通过Key-Value键值方式来存储数据,其中Key和Value是简单的对象,也可以是复杂的对象。Key作为唯一的标识符,优点是查找速度快,这方面明显优于关系型数据库,缺点是无法像关系型数据库一样使用条件过滤(比如WHERE),如果你不知道去哪里找数据,就要遍历所有的键,这样就会消耗大量的计算。
键值型数据库典型的使用场景是作为内存缓存。Redis
是最流行的键值型数据库。
==文档型数据库==
此类数据库可存放并获取文档,可以是XML、JSON等格式。在数据库中文档作为处理信息的基本单位,一个文档就相当于一条记录。文档数据库所存放的文档,就相当于键值数据库所存放的“值”。MongoDB
是最流行的文档型数据库。此外,还有CouchDB等。
==搜索引擎数据库==
虽然关系型数据库采用了索引提升检索效率,但是针对全文索引效率却较低。搜索引擎数据库是应用在搜索引擎领域的数据存储形式,由于搜索引擎会爬取大量的数据,并以特定的格式进行存储,这样在检索的时候才能保证性能最优。核心原理是“倒排索引”。
典型产品: Solr
、Elasticsearch
、Splunk
等。
==列式数据库==
列式数据库是相对于行式存储的数据库,Oracle、MySQL、sQL Server等数据库都是采用的行式存储(Row-based),而列式数据库是将数据按照列存储到数据库中,这样做的好处是可以大量降低系统的V/O,适合于分布式文件系统,不足在于功能相对有限。典型产品:HBase
等。
==图形数据库==
图形数据库,利用了图这种数据结构存储了实体(对象)之间的关系。图形数据库最典型的例子就是社交网络中人与人的关系,数据模型主要是以节点和边(关系)来实现,特点在于能高效地解决复杂的关系问题。
图形数据库顾名思义,就是一种存储图形关系的数据库。它利用了图这种数据结构存储了实体(对象)之间的关系。关系型数据用于存储明确关系的数据,但对于复杂关系的数据存储却有些力不从心。如社交网络中人物之间的关系,如果用关系型数据库则非常复杂,用图形数据库将非常简单。典型产品: Neo4J、InfoGrid等。
4. 关系型数据库设计的规则
4.1.1 表的关联关系
- 表与表之间的数据记录有关系(
relationship
)。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。 - 四种:一对一关联、一对多关联、多对多关联、自我引用
4.1.2 一对一关联(one-to-one)
在实际的开发中应用不多,因为一对一可以创建成一张表。
- 举例:设计
学生表
:学号、姓名、手机号码、班级、系别、身份证号码、家庭住址、籍贯、紧急联系人、…- 拆为两个表:两个表的记录是——对应关系。
基础信息表
(常用信息):学号、姓名、手机号码、班级、系别、…档案信息表
(不常用信息):学号、身份证号码、家庭住址、籍贯、紧急联系人、…
- 两种建表原则:
- 外键唯一:主表的主键和从表的外键(唯一),形成主外键关系,外键唯一。
- 外键是主键:主表的主键和从表的主键,形成主外键关系。
4.1.3 一对多关系(one-to-many)
常见实例场景:客户表和订单表
,分类表和商品表
,部门表和员工表
。
- 举例:
- 员工表:编号、姓名、…、所属部门。
- 部门表:编号、名称、简介
- 一对多建表原则:在从表(多方)创建一个字段,字段作为外键指向主表(一方)的主键
4.1.4 多对多(many-to-many)
要表示多对多关系,必须创建第三个表,该表通常称为联接表,它将多对多关系划分为两个一对多关系。将这两个表的主键都插入到第三个表中。
Mysql环境搭建
1. Mysql的下载安装和配置(社区版)
==1.下载地址==
==2.打开官网,点击DOWNLOADS
==
==3.点击MySQL Community (GPL) Downloads
==
==4.打开官网,点击MySQL Community Server
==
==5.下载最新版本==
==6.下载指定版本==
2. Mysql的安装
安装路径不能包含中文或中文的空格。
以上就完成了Mysql的安装,但是还需要进行Mysql的配置。
设置密码,默认用户名是root,也可以添加其他用户
完成以上步骤Mysql配置完成
3. 配置MySQL8.0环境变量
如果不配置MysQL环境变量,就不能在命令行直接输入MysQL登录命令。下面说如何配置MysQL的环境变量:
步骤1:在桌面上右击【此电脑】图标,在弹出的快捷菜单中选择【属性】菜单命令。
步骤2:打开【系统】窗口,单击【高级系统设置】链接。
步骤3:打开【系统属性】对话框,选择【高级】选项卡,然后单击【环境变量】按钮。
步骤4∶打开【环境变量】对话框,在系统变量列表中选择path变量。
步骤5∶单击【编辑】按钮,在【编辑环境变量】对话框中,将MySQL应用程序的bin目录(C:\ProgramFiles\MySQL|MySQL Server 8.0\bin)添加到变量值中,用分号将其与其他路径分隔开。
步骤6:添加完成之后,单击【确定】按钮,这样就完成了配置path变量的操作,然后就可以直接输入MySQL命令来登录数据库了。
==测试是否安装成功==
快捷键【win+R】输入cmd回车,输入命令mysql --version或mysql -V
提示版本信息即安装成功
4. 使用图形化工具可能会出现的问题
安装默认选择8.0新增的加密方式,选5.x版本无此问题。
出现这个原因是MySQL8之前的版本中加密规则是mysql_native_password,而在MySQL8之后,加密规则是
caching_sha2_password。解决问题方法有两种。
第一种是升级图形界面工具版本,二是把MySQL8用户登录密码加密规则还原成mysql_native_password。
第二种解决方案如下,用命令行登录MysQL数据库之后,执行如下命令修改用户密码加密规则并更新用户密码,这里修改用户名为“root@localhost”的用户密码规则为“mysql_native_password”,密码值为“123456
‘,如图所示。
1 | # 使用mysql数据库 |
基本的SELECT语句
1. SQL语言的分类
SQL语言在功能上主要分为如下三大类:
- ==DDL(Data Definition Languages、数据库定义语言)==,这些语句定义了不同的数据库、表、视图、索引等数据库对象,还可以用来创建、删除、修改数据库表的结构
- 主要的关键字包括
CREATE
、DELETE
、ALTER
等。
- 主要的关键字包括
- ==DML(Data Mainpulation Language、数据库操作语言)==,用于添加、删除、更新和查询数据库记录,并检查数据完整性。
- 主要的语句关键字包括
INSERT
、DELETE
、UPDATE
、SELECT
等。 - ==
SELECT
是SQL的基础,最为重要。==
- 主要的语句关键字包括
- ==DCL(Data Control Language、数据库控制语言)==,用于定义数据库、表、字段、用户的访问权限和安全级别。
- 主要的语句关键字包括
GRANT
、REVOKE
,COMMIT
,ROLLBACK
,SAVEPOINT
。
- 主要的语句关键字包括
因为查询语句使用的非常的频繁,所以很多人把查询语句单拎出来一类:DQL (数据查询语言)。还有单独将COMMIT、ROLLBACK 取出来称为TCL (Transaction Control Language,事务控制语言)
2. SQL语言的规则与规范
2.1 基本规则
- SQL可以写在一行或者多行。为了提高可读性,各字句分行写,必要时使用缩进
- 每条命令以
;
、\g
或\G
结束 - 关键字不能被缩写也不能分行
- 关于标点符号
- 必须保证所有的()、单引号、双引号是成双成对的
- 必须使用英文状态下的半角输入方式
- 字符串型和日期类型数据可以使用可以使用单引号(’’)表示
- 列的别名,尽量使用双引号(’’),而且不建议省略as
2.2 SQL大小写规范
- MySQL在Window环境下是大小写不敏感的
- MySQL在Linux环境下是大小写敏感的
- 数据库名、表名、表的别名、变量名是严格区分大小写的
- 关键字、函数名、列名(或字段名)、列的别名(字段的别名)是忽略大小写的。
- 推荐采用统一的书写规范
- 数据库名、表名、表别名、字段名、字段别名等都小写
- SQL关键字、函数名、绑定变量等都大写
2.3 注释
1 | # 单行注释 |
2.4 命名规则
- 数据库、表名不得超过30个字符,变量名限制为29个。必须只能包含A-Z,a-z,O-9,_共63个字符
- 数据库名、表名、字段名等对象名中间不要包含空格
- 同一个MysQL软件中,数据库不能同;同一个库中,表不能重名;同一个表中,字段不能重名
- 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在sQL语句中使用`(着重号)引起来
- 保持字段名和类型的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了
2.5 数据的导入
方式一:在命令行客户端登陆mysql,使用source
命令
1 | # source 文件名全路径 |
方式二:图形化工具导入
3. 基本的SELECT语句
3.1 SELECT …
1 | SELECT 1+1,1*3; |
3.2 SELECT … FROM
语法:
1 | SELECT # 标识选择那些列 |
- 选择全部列
1 | # *:表示查询表中所有的字段(列) |
- 查询指定列
1 | SELECT employee_id,email,phone_number FROM employees; |
3.3 列的别名
as:全称是
alias
(别名),可以省略列的别名可以使用
""
引起来,不可以使用单引号举例:
1 | SELECT employee_id emp_id,phone_number phone FROM employees; |
3.4 去除重复行
使用DISTINCT
关键字去除重复行
- 举例
1 | SELECT DISTINCT department_id FROM employees; |
针对于:
1 | SELECT DISTINCT department_id,salay FROM employees; |
DISTINCT 其实是对后面所有列名的组合进行去重。如果你想要看都有哪些不同的部门(department_id),只需要写 DISTINCT department_id 即可,后面不需要再加其他的列名了。
3.5 空值参与运算
所有运算符或列值遇到null值,运算的结果都为null
空值:
null
null
不等于0
,''
,'null'
在 MySQL 里面,空值是占用空间的。
3.6 着重号(``)
我们需要保证表中的字段、表名等没有和保留字、数据库系统或常用方法冲突。如果真的相同,请在SQL语句中使用一对``(着重号)引起来。
- 举例
1 | # 错误写法,ORDER是一个关键字 |
4. 显示表结构
使用DESCRIBE
或DESC
关键字
1 | DESCRIBE employees; |
其中,各个字段的含义分别解释如下:
Field:表示字段名称。
Type:表示字段类型,这里 barcode、goodsname 是文本型的,price 是整数类型的。
Null:表示该列是否可以存储NULL值。
Key:表示该列是否已编制索引。PRI表示该列是表主键的一部分;UNI表示该列是UNIQUE索引的一
部分;MUL表示在列中某个给定值允许出现多次。
Default:表示该列是否有默认值,如果有,那么值是多少。
Extra:表示可以获取的与给定列有关的附加信息,例如AUTO_INCREMENT等。
5. 过滤数据
使用WHERE
关键字
- 语法
1 | SELECT 字段1,字段2 |
- 举例
1 | # 查询员工月薪大于 > 10000的用户 |
6. 课后练习
1 | SELECT * FROM employees; |
运算符
1.算术运算符
- 举例
1 | # 在SQL中+号没有链接作用,仅表示加法运算 |
2.比较运算符
比较运算符用来对表达式左边的操作数和右边的操作数进行比较,比较的结果为真则返回1,比较结果为假则返回0,其他情况返回NULL
比较运算符经常被用来作为SELECT查询语句的条件来使用,返回符合条件的结果记录。
- 举例
1 | SELECT 1=2,1='1',1='a' FROM DUAL; # 字符串存在隐式转换,如果数组转换不成功,则看做0 |
2.1 安全等于运算符
安全等于运算符<=>
与等于运算符=
作用是相似的,唯一区别是<=>
运算符可以对==NULL==进行判断。在两个操作数为NULL时,其返回值为1,而不为NULL;当一个操作数为NULL时,其返回值为0,而不为NULL。
- 举例
1 | SELECT 1 <=> NULL,NULL <=> NULL FROM DUAL; |
此外还有符号类型的运算符
- 举例
1 | # IS NULL / ISNULL / IS NOT NULL |
2.2 LIKE运输符
%
代表0个或多个不确定的字符
1 | # 获取包含‘a’的字符 |
-
代表一个不确定的字符,如果需要获取指定字符就是’_’的需要使用转义字符\
,或者使用ESCAPE
。如果使用的是\
,则可以省略ESCAPE
,如果不是则需要加上ESCAPE
1 | # 获取第二个字符是_并且第三个字符是‘a’的名称 |
3. 逻辑运算符
逻辑运算符主要用来判断表达式的真假,在MySQL中,逻辑运算符的返回结构为1,0或者NULL。
MySQL支持4种逻辑运算符如下:
1 | SELECT employee_id,last_name,salary,department_id FROM employees WHERE NOT department_id = 50 |
4. 位运算符
位运算符是在二进制数上进行计算的运算符。位运算符会优先将操作数变成二进制数,然后进行位运算,最后将计算结构从二进制变回十进制数。
1 | SELECT 12 & 5, 12 | 5, 12 ^ 5 FROM DUAL;# 4,13,9 |
5. 课后练习
1 | # 1.选择工资不在5000到12000的员工的姓名和工资 |
排序与分页
1. 排序数据
1.1 排序规则
- 使用
ORDER BY
子句排序- ASC(ascend):升序,默认排序规则
- DESC(descend):降序
- ORDER BY字句在WHERE的结尾
1.2 单列排序
1 | # 对所有员工的工资进行升序排序 |
1.3 多列排序
1 | # 对所有员工的工资进行升序排序,如果工资相同,则对员工的部门进行降序排序 |
- 可以使用不在SELECT列表中的列排序
- 在对多列排序的时候,首先必须是第一列有相同的值,才会对第二列进行排序。如果第一列都撒唯一的,则不会对第二列进行排序。
2. 分页
语法格式
LIMIT 位置偏移量,行数
Mysql8.0语法规则
LIMIT 行数 OFFSET 位置偏移量
位置偏移量为
0
可以省略分页规则
1 | # 显示第1页数据,每页20条 |
- 注意:LIMIT字句必须放在整个SELECT语句的最后。
约束返回结构的数量可以==减少数据库表的网络传播量==,==也可以提升查询效率==。好处就是我们知道结果只有1条,就可以使用limit 1
,告诉SELECT语句只需要查询一条记录即可,这样做的好处就是不需要扫描完整的表,只需要检索到一条符合条件的记录即可返回。
3. 课后练习
1 | #1. 查询员工的姓名和部门号和年薪,按年薪降序,按姓名升序显示 |
多表查询
多表查询,也称关联查询,指两个或更多个表之间完成查询操作。
前提条件:这些一起查询的表之间是有关系的(一对一、一对多),它们之间一定是有关联字段,这个关联字段可能建立了外键,也可能没有建立外键。比如:员工表和部门表,两个表之间依靠部门编号关联
1. 笛卡尔积(或交叉连接)的理解
笛卡尔积是一个数学运算。假设我有两个集合X和Y,那么X和Y的笛卡尔积就是X和Y的所有可能组合。也就是第一个对象来自于X,第二个对象来自于Y的所有可能。组合的个数即为两个集合中元素个数的乘积数。
1 | # 笛卡尔积错误,没加链接条件 |
1 | # 练习:查询员工的employee_id,last_name,department_name,city |
2. 多表查询的分类讲解
- 等值连接和非等值链接(SQL92语法)
1 | # 等值链接 |
- 自链接和非自链接(SQL92语法)
1 | # 自链接查询,查询员工id,员工姓名及管理者id和姓名 |
内链接和外链接
内链接:合并具有同一列的两个以上的表的行,结果集中不包含一个表于另一个表不匹配的行
外链接:两个表在链接过程中除了返回满足链接条件的行以外,还返回左(或右)表中不满足条件的行,这种称为左(或右)外链接。没有匹配的行时,结果集中相应的列为空(NULL)。
- 如果是左外链接,则链接条件左边的表也称为
主表
,右边的表称为从表
- 如果是右外链接,则链接条件右边的表也称为
主表
,左边的表称为从表
- 如果是左外链接,则链接条件左边的表也称为
3. SQL99语法实现夺表查询
SQL99语法多表查询规则
1 | SELECT table1.column, table2.column,table3.column |
语法说明:
- 可以使用 ON 子句指定额外的连接条件。
- 这个连接条件是与其它条件分开的。
- ON 子句使语句具有更高的易读性。
- 关键字
JOIN
、INNER JOIN
、CROSS JOIN
的含义是一样的,都表示内连接
3.1 内链接使用(INNER JOIN
)实现
- 语法
1 | SELECT 字段列表 |
- 举例
1 | # SQL99语法实现内链接 |
3.2 外连接(OUTER JOIN
)的实现
3.2.1 左外链接
1 | # SQL99语法实现左外链接,OUTER可以省略 |
3.2.2 右外链接
1 | # SQL99语法实现左外链接,OUTER可以省略 |
3.2.3 全外链接(满外链接)
1 | # SQL99语法实现全外链接,OUTER可以省略,MYSQL不支持FULL OUTER JOIN |
4. UNION关键字的使用
==合并查询结果==
利用UNION关键字,可以给出多条SELECT语句,并将他们的结果组合成单个结果集。合并时,两个表对应的列数和数据类型必须相同,并且对应。各个SELECT语句之间使用UNION
或UNION ALL
关键字分隔
语法格式
1 | SELECT column,... FROM table1 |
==UNION操作符==
UNION操作符返回两个查询的结果集的并集,去除重复记录。
==UNION ALL操作符==
UNION ALL操作符返回两个查询的结果集的并集,对于两个结果集的重复部分,不去重。
注意:执行UNION ALL语句时所需要的资源比UNION语句少。如果明确知道合并数据后的结果数据 不存在重复数据,或者不需要去除重复的数据,则尽量使用UNION ALL语句,以提高数据查询的效 率。
5. 7中JOIN的实现
- 中图:内链接
1 | SELECT e.employee_id,d.department_name |
- 左上图:左外链接
1 | SELECT e.employee_id,d.department_name |
- 右上图:右外链接
1 | SELECT e.employee_id,d.department_name |
- 左中图:左外链接取右表为空的部分
1 | SELECT e.employee_id,d.department_name |
- 右中图:右外链接取左表为空的部分
1 | SELECT e.employee_id,d.department_name |
- 左下图: 左上图
UNION ALL
右中图(或左中图UNION ALL
右上图 )
1 | SELECT e.employee_id,d.department_name |
- 右下图
1 | SELECT e.employee_id,d.department_name |
6. SQL99语法新特性
6.1 自然链接
SQL99在SQL92的基础上提供了一些特殊语法,比如NATURAL JOIN
用来表示自然连接。我们可以把自然连接理解为SQL92中的等值连接。它会帮你自动查询两张连接表中所有相同的字段
,然后进行等值链接
。
1 | SELECT e.employee_id,e.last_name,d.department_name |
6.2 USING链接
SQL99还支持中使用USING
对同名字段进行值链接。但只能配合JOIN关键字一起使用。
1 | SELECT e.employee_id,d.department_name |
7. 课后练习
1 | # 1.显示所有员工的姓名,部门号和部门名称。 |
单行函数
单行函数
- 操作数据对象
- 接受参数返回一个结果
- 只对一行进行变换
- 每行返回一个结果
- 可以嵌套
- 参数可以是一列或一个值
1. 数值函数
1.1 基本函数
函数 | 用法 |
---|---|
ABS(x) | 返回x的绝对值 |
SIGN(X) | 返回X的符号。正数返回1,负数返回-1,0返回0 |
PI() | 返回圆周率的值 |
CEIL(x),CEILING(x) | 返回大于或等于某个值的最小整数 |
FLOOR(x) | 返回小于或等于某个值的最大整数 |
LEAST(e1,e2,e3…) | 返回列表中的最小值 |
GREATEST(e1,e2,e3…) | 返回列表中的最大值 |
MOD(x,y) | 返回X除以Y后的余数 |
RAND() | 返回0~1的随机值 |
RAND(x) | 返回0~1的随机值,其中x的值用作种子值,相同的X值会产生相同的随机 数 |
ROUND(x) | 返回一个对x的值进行四舍五入后,最接近于X的整数 |
ROUND(x,y) | 返回一个对x的值进行四舍五入后最接近X的值,并保留到小数点后面Y位 |
TRUNCATE(x,y) | 返回数字x截断为y位小数的结果 |
SQRT(x) | 返回x的平方根。当X的值为负数时,返回NULL |
1.2 角度与弧度互换函数
函数 | 用法 |
---|---|
RADIANS(x) | 将角度转化为弧度,其中,参数x为角度值 |
DEGREES(x) | 将弧度转化为角度,其中,参数x为弧度值 |
1.3 三角函数
函数 | 用法 |
---|---|
SIN(x) | 返回x的正弦值,其中,参数x为弧度值 |
ASIN(x) | 返回x的反正弦值,即获取正弦为x的值。如果x的值不在-1到1之间,则返回NULL |
COS(x) | 返回x的余弦值,其中,参数x为弧度值 |
ACOS(x) | 返回x的反余弦值,即获取余弦为x的值。如果x的值不在-1到1之间,则返回NULL |
TAN(x) | 返回x的正切值,其中,参数x为弧度值 |
ATAN(x) | 返回x的反正切值,即返回正切值为x的值 |
ATAN2(m,n) | 返回两个参数的反正切值 |
COT(x) | 返回x的余切值,其中,X为弧度值 |
1.4 指数和对数
函数 | 用法 |
---|---|
POW(x,y),POWER(X,Y) | 返回x的y次方 |
EXP(X) | 返回e的X次方,其中e是一个常数,2.718281828459045 |
LN(X),LOG(X) | 返回以e为底的X的对数,当X <= 0 时,返回的结果为NULL |
LOG10(X) | 返回以10为底的X的对数,当X <= 0 时,返回的结果为NULL |
LOG2(X) | 返回以2为底的X的对数,当X <= 0 时,返回NULL |
1.5 进制之间的转换
函数 | 用法 |
---|---|
BIN(x) | 返回x的二进制编码 |
HEX(x) | 返回x的十六进制编码 |
OCT(x) | 返回x的八进制编码 |
CONV(x,f1,f2) | 返回f1进制数变成f2进制数 |
2. 字符串函数
函数 | 用法 |
---|---|
ASCII(S) | 返回字符串S中的第一个字符的ASCII码值 |
CHAR_LENGTH(s) | 返回字符串s的字符数。作用与CHARACTER_LENGTH(s)相同 |
LENGTH(s) | 返回字符串s的字节数,和字符集有关 |
CONCAT(s1,s2,……,sn) | 连接s1,s2,……,sn为一个字符串 |
CONCAT_WS(x,s1,s2,……,sn) | 同CONCAT(s1,s2,…)函数,但是每个字符串之间要加上x |
INSERT(str,idx,len,replacestr) | 将字符串str从第idx位置开始,len个字符长的子串替换为字符串replacestr |
REPLACE(str, a, b) | 用字符串b替换字符串str中所有出现的字符串a |
UPPER(s) 或 UCASE(s) | 将字符串s的所有字母转成大写字母 |
LOWER(s) 或LCASE(s) | 将字符串s的所有字母转成小写字母 |
LEFT(str,n) | 返回字符串str最左边的n个字符 |
RIGHT(str,n) | 返回字符串str最右边的n个字符 |
LPAD(str, len, pad) | 用字符串pad对str最左边进行填充,直到str的长度为len个字符 |
RPAD(str ,len, pad) | 用字符串pad对str最右边进行填充,直到str的长度为len个字符 |
LTRIM(s) | 去掉字符串s左侧的空格 |
RTRIM(s) | 去掉字符串s右侧的空格 |
TRIM(s) | 去掉字符串s开始与结尾的空格 |
TRIM(s1 FROM s) | 去掉字符串s开始与结尾的s1 |
TRIM(LEADING s1 FROM s) | 去掉字符串s开始处的s1 |
TRIM(TRAILING s1 FROM s) | 去掉字符串s结尾处的s1 |
REPEAT(str, n) | 返回str重复n次的结果 |
SPACE(n) | 返回n个空格 |
STRCMP(s1,s2) | 比较字符串s1,s2的ASCII码值的大小 |
SUBSTR(s,index,len) | 返回从字符串s的index位置其len个字符,作用与SUBSTRING(s,n,len)、 MID(s,n,len)相同 |
LOCATE(substr,str) | 返回字符串substr在字符串str中首次出现的位置,作用于POSITION(substr IN str)、INSTR(str,substr)相同。未找到,返回0 |
ELT(m,s1,s2,…,sn) | 返回指定位置的字符串,如果m=1,则返回s1,如果m=2,则返回s2,如 果m=n,则返回sn |
FIELD(s,s1,s2,…,sn) | 返回字符串s在字符串列表中第一次出现的位置 |
FIND_IN_SET(s1,s2) | 返回字符串s1在字符串s2中出现的位置。其中,字符串s2是一个以逗号分 隔的字符串 |
REVERSE(s) | 返回s反转后的字符串 |
NULLIF(value1,value2) | 比较两个字符串,如果value1与value2相等,则返回NULL,否则返回 value1 |
3. 日期和时间函数
3.1 获取日期、时间
函数 | 用法 |
---|---|
CURDATE() ,CURRENT_DATE() | 返回当前日期,只包含年、 月、日 |
CURTIME() , CURRENT_TIME() | 返回当前时间,只包含时、 分、秒 |
NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() / LOCALTIMESTAMP() | 返回当前系统日期和时间 |
UTC_DATE() | 返回UTC(世界标准时间) 日期 |
UTC_TIME() | 返回UTC(世界标准时间) 时间 |
1 | # 获取日期、时间、当前时间 |
3.2 日期和时间戳的转换
函数 | 用法 |
---|---|
UNIX_TIMESTAMP() | 以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() - >1634348884 |
UNIX_TIMESTAMP(date) | 将时间date以UNIX时间戳的形式返回。 |
FROM_UNIXTIME(timestamp) | 将UNIX时间戳的时间转换为普通格式的时间 |
1 | # 日期和时间戳的转换 |
3.3 获取月份、星期、星期数、天数等函数
函数 | 用法 |
---|---|
YEAR(date) / MONTH(date) / DAY(date) | 返回具体的日期值 |
HOUR(time) / MINUTE(time) / SECOND(time) | 返回具体的时间值 |
MONTHNAME(date) | 返回月份:January,… |
DAYNAME(date) | 返回星期几:MONDAY,TUESDAY…..SUNDAY |
WEEKDAY(date) | 返回周几,注意,周1是0,周2是1,。。。周日是6 |
QUARTER(date) | 返回日期对应的季度,范围为1~4 |
WEEK(date) , WEEKOFYEAR(date) | 返回一年中的第几周 |
DAYOFYEAR(date) | 返回日期是一年中的第几天 |
DAYOFMONTH(date) | 返回日期位于所在月份的第几天 |
DAYOFWEEK(date) | 返回周几,注意:周日是1,周一是2,。。。周六是 7 |
1 | # 获取年、月、日、时、分、秒 |
3.4 日期操作函数
函数 | 用法 |
---|---|
EXTRACT(type FROM date) | 返回指定日期中特定的部分,type返回指定的值 |
type的取值
type的取值 | 描述 |
---|---|
MICROSECOND | 返回毫秒数 |
SECOND | 返回秒数 |
MINUTE | 返回分钟数 |
HOUR | 返回小时数 |
DAY | 返回天数 |
WEEK | 返回日期在一年中的第几个星期 |
MONTH | 返回日期在一年中的第几个月 |
QUARTER | 返回日期在一年中的第几个季度 |
YEAR | 返回日期的年份 |
SECOND_MICROSECOND | 返回秒和毫秒值 |
MINUTE_MICROSECOND | 返回分钟和毫秒值 |
MINUTE_SECOND | 返回分钟和秒值 |
HOUR_MICROSECOND | 返回小时和毫秒值 |
HOUR_SECOND | 返回小时和秒值 |
HOUR MINUTE | 返回小时和分钟值 |
DAY_MICROSECOND | 返回天和毫秒值 |
DAY_SECOND | 返回天和秒值 |
DAY_MINUTE | 返回天和分钟值 |
DAY_HOUR | 返回天和小时 |
YEAR_MONTH | 返回年和月 |
1 | # 获取当前时间的分钟数、一年中的第几个星期、一年中的第几个季度、分钟和秒值 |
3.5 时间和秒钟转换的函数
函数 | 用法 |
---|---|
TIME_TO_SEC(time) | 将 time 转化为秒并返回结果值。转化的公式为: 小时*3600+分钟*60+秒 |
SEC_TO_TIME(seconds) | 将 seconds 描述转化为包含小时、分钟和秒的时间 |
1 | # 获取当前时间的秒值(今天0点到当前时间的秒数)和秒数转换后的时间值 |
3.6 计算日期和时间的函数
函数 | 用法 |
---|---|
DATE_ADD(datetime, INTERVAL expr type) ,ADDDATE(date,INTERVAL expr type) | 返回与date相差INTERVAL时间段的日期时间 |
DATE_SUB(date,INTERVAL expr type), SUBDATE(date,INTERVAL expr type) | 返回与date相差INTERVAL时间间隔的日期 |
type取值
1 | # 计算当前时间后一年和前一年的时间 |
==第二组:==
函数 | 用法 |
---|---|
ADDTIME(time1,time2) | 返回time1加上time2的时间。当time2为一个数字时,代表的是 秒 ,可以为负数 |
SUBTIME(time1,time2) | 返回time1减去time2后的时间。当time2为一个数字时,代表的是 秒 ,可以为负数 |
DATEDIFF(date1,date2) | 返回date1 - date2的日期间隔天数 |
TIMEDIFF(time1, time2) | 返回time1 - time2的时间间隔 |
FROM_DAYS(N) | 返回从0000年1月1日起,N天以后的日期 |
TO_DAYS(date) | 返回日期date距离0000年1月1日的天数 |
LAST_DAY(date) | 返回date所在月份的最后一天的日期 |
MAKEDATE(year,n) | 针对给定年份与所在年份中的天数返回一个日期 |
MAKETIME(hour,minute,second) | 将给定的小时、分钟和秒组合成时间并返回 |
PERIOD_ADD(time,n) | 返回time加上n后的时间 |
1 | SELECT |
3.7 日期格式化与解析
函数 | 用法 |
---|---|
DATE_FORMAT(date,fmt) | 按照字符串fmt格式化日期date值 |
TIME_FORMAT(time,fmt) | 按照字符串fmt格式化时间time值 |
GET_FORMAT(date_type,format_type) | 返回日期字符串的显示格式 |
STR_TO_DATE(str, fmt) | 按照字符串fmt对str进行解析,解析为一个日期 |
上述 非GET_FORMAT
函数中fmt参数常用的格式符:
格 式 符 | 说明 | 格式 符 | 说明 |
---|---|---|---|
%Y | 4位数字表示年份 | %y | 表示两位数字表示年份 |
%M | 月名表示月份(January,….) | %m | 两位数字表示月份 (01,02,03。。。) |
%b | 缩写的月名(Jan.,Feb.,….) | %c | 数字表示月份(1,2,3,…) |
%D | 英文后缀表示月中的天数 (1st,2nd,3rd,…) | %d | 两位数字表示月中的天数(01,02…) |
%e | 数字形式表示月中的天数 (1,2,3,4,5…..) | ||
%H | 两位数字表示小数,24小时制 (01,02..) | %h 和%I | 两位数字表示小时,12小时制 (01,02..) |
%k | 数字形式的小时,24小时制(1,2,3) | %l | 数字形式表示小时,12小时制 (1,2,3,4….) |
%i | 两位数字表示分钟(00,01,02) | %S 和%s | 两位数字表示秒(00,01,02…) |
%W | 一周中的星期名称(Sunday…) | %a | 一周中的星期缩写(Sun., Mon.,Tues.,..) |
%w | 以数字表示周中的天数 (0=Sunday,1=Monday….) | ||
%j | 以3位数字表示年中的天数(001,002…) | %U | 以数字表示年中的第几周, (1,2,3。。)其中Sunday为周中第一 天 |
%u | 以数字表示年中的第几周, (1,2,3。。)其中Monday为周中第一 天 | ||
%T | 24小时制 | %r | 12小时制 |
%p | AM或PM | %% | 表示% |
1 | SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),TIME_FORMAT(NOW(), '%H:%i:%s') FROM DUAL; |
GET_FORMAT函数中date_type
和format_type
参数取值如下:
4. 流程控制函数
函数 | 用法 |
---|---|
IF(value,value1,value2) | 如果value的值为TRUE,返回value1,否则返回value2 |
IFNULL(value1, value2) | 如果value1不为NULL,返回value1,否则返回value2 |
CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 END | 相当于Java的if…else if…else… |
CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值1 THEN END | 相当于Java的switch…case… |
1 | SELECT IF(1 > 0,'正确','错误') FROM DUAL; |
1 | SELECT IFNULL(null,'Hello Word') FROM DUAL; |
1 | SELECT CASE |
1 | SELECT CASE 1 |
5. 加密和解密函数
函数 | 用法 |
---|---|
PASSWORD(str) | 返回字符串str的加密版本,41位长的字符串。加密结果不可逆 ,常用于用户的密码加密 |
MD5(str) | 返回字符串str的md5加密后的值,也是一种加密方式。若参数为NULL,则会返回NULL |
SHA(str) | 从原明文密码str计算并返回加密后的密码字符串,当参数为NULL时,返回NULL。 SHA加密算法比MD5更加安全 。 |
ENCODE(value,password_seed) | 返回使用password_seed作为加密密码加密value |
DECODE(value,password_seed) | 返回使用password_seed作为加密密码解密value |
聚合函数
1. 常见的聚合函数
- AVG()
- SUM()
- MAX()
- MIN()
- COUNT()
1.1 AVG 和 SUM函数
可以对数值型数据使用AVG 和 SUM 函数。
1 | SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary) FROM employees; |
1.2 MAX 和 MIN函数
可以对任意数据类型的数据使用 MIN 和 MAX 函数。
1 | SELECT MIN(hire_date), MAX(hire_date) FROM employees; |
1.3 COUNT 函数
COUNT()返回表中记录总数,适用于*任意数据类型。
1 | SELECT COUNT(*) FROM employees WHERE department_id = 50; |
问题:用count(*),count(1),count(列名)谁好呢?
其实,对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。
Innodb引擎的表用count(*),count(1)直接读行数,复杂度是O(n),因为innodb真的要去数一遍。但好
于具体的count(列名)。
能不能使用count(列名)替换count(*)?
不要使用 count(列名)来替代 count(*) , count(*) 是 SQL92 定义的标准统计行数的语法,跟数
据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
2. GROUP BY
可以使用关键字GROUP BY
进行分组。搭配聚合函数使用。
1 | # 查询各个部门的平均工资 |
==使用多个列分组==
1 | # 查询各个department_id、job_id的平均工资 |
使用 WITH ROLLUP 关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。
1 | SELECT department_id,AVG(salary) FROM employees WHERE department_id > 80 GROUP BY department_id WITH ROLLUP; |
注意:
SELECT中出现的非组函数的字段必须声明在GROUP BY中,反之,GROUP BY中声明的字段可以不出现在SELECT中。
当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。
3. HAVING
3.1 基本使用
过滤分组:HAVING
子句(用来过滤数据)
- 行已经被分组。
- 使用了聚合函数。
- 满足HAVING 子句中条件的分组将被显示。
- HAVING 不能单独使用,必须要跟 GROUP BY 一起使用。
- 如果过滤条件中使用了聚合函数,则必须使用HAVING替换WHERE。
- HAVGIN声明必须在GROUP BY后面。
1 | # 查询部门最高工资大于1万的 |
3.2 WHERE 和 HAVING 的对比
优点 | 缺点 | |
---|---|---|
WHERE | 先筛选数据再关联,执行效率高 | 不能使用分组中的计算函数进行筛选 |
HAVING | 可以使用分组中的计算函数 | 在最后的结果集中进行筛选,执行效率较低 |
==开发中的选择:==
WHERE和HAVING也不是互相排斥的,我们可以在一个查询里面同时使用WHERE和HAVING。包含分组统计函数的条件用HAVING,普通条件用WHERE。这样,我们就既利用了WHERE条件的高效快速,又发挥了HAVING可以使用包含分组统计函数的查询条件的优点。当数据量特别大的时候,运行效率会有很大的差别。
4. SELECT的执行过程
4.1 查询数据的方式
1 | #方式1: |
关键字的顺序
1 | SELECT...FROM...WHERE...GROUP BY...HAVING...ORDER BY...LIMIT... |
SELECT语句的执行顺序
1 | FROM -> ON -> (LEFT/RIGHT JOIN) -> WHERE -> GROUP BY -> HAVING -> SELECT column -> DISTINCT -> ORDER BY -> LIMIT |
4.2 SELECT的执行过程
SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:
首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1;
通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2;
添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3。
当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据(FROM ... LEFT/RIGHT JOIN 后的数据)
。当我们拿到了查询数据表的原始数据,也就是最终的虚拟表
vt1
,就可以在此基础上再进行WHERE阶段
。在这个阶段中,会根据 vt1表的结果进行筛选过滤,得到虚拟表vt2
。然后进入第三步和第四步,也就是
GROUP 和 HAVING 阶段
。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表vt3
和vt4
。当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到
SELECT 和 DISTINCT阶段
。
首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表vt5-1
和vt5-2
。
当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是
ORDER BY 阶段
,得到虚拟表vt6
。最后在 vt6 的基础上,取出指定行的记录,也就是
LIMIT 阶段
,得到最终的结果,对应的是虚拟表vt7 。
当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。
同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的
关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。
5. 课后练习
1 | #1.where子句可否使用组函数进行过滤? |
子查询
子查询是指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL4.1开始引入。
SQL中子查询的使用大大增强了SELECT查询的能力,因为很多时候查询需要从结果集中获取数据,或者需要从同一个表中先计算得出一个数据结果,然后与这个结果(可能是某个标量,也可能是某个集合)进行比较。
1. 子查询的基本使用
1 | SELECT last_name,salary |
- 子查询(内查询)在主查询之间一次执行完成。
- 子查询记的结果被主查询(外查询)使用。
- 注意事项
- 子查询要包含在括号内
- 将子查询放到比较条件的右侧
- 单行操作符对应单行子查询,多行操作符对应多行子查询。
1.1 子查询的分类
分类方式一:
按内查询的结果返回一条还是多条记录,将子查询分为单行子查询
、多行子查询
。
分类方式二:
按照是否被执行多次,将子查询划分为相关(关联)子查询
和不相关(非关联)子查询
。
子查询从数据表中查询了数据结果,如果这个数据结果只能执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询
。
同样,如果子查询需要执行多次,即采用循环的方式,从外部查询开始,每次都传入子查询进行查询,然后在将结果反馈给外部,这种嵌套的执行方式就称为相关子查询
。
2. 单行子查询
2.1 单行比较操作符
操作符:=
、>
、>=
、<
、<=
、<>
2.2 代码案例
1 | # 查询工资大于149号员工的工资的员工信息 |
1 | # 返回job_id与141号员工相同,salary比143号员工多的员工姓名,job_id和工资 |
1 | # 返回公司工资最少的员工的lastname,job_id,salary |
1 | # 查询与141号员工的manager_ id和department_id相同的其他员工的employee_id,manager_id, department_id |
1 | # 查询最低工资大于50号部门最低工资的部门id和其最低工资 |
1 | # 显示员工的employee id,last name和locationo |
2.3 空值问题
1 | SELECT last_name,job_id |
3. 多行子查询
- 多行子查询
- 内查询返回多行
- 使用多行子查询比较操作符
3.1 多行比较操作符
操作符 | 描述 |
---|---|
IN |
等于列表的任意一个 |
ANY |
需要和单行比较操作符一起使用,和子查询返回某一个值的比较 |
ALL |
需要和单行比较操作符一起使用,和子查询返回的所有值比较 |
SOME |
实际上是ANY的别名,作用相同,一般常使用ANY |
3.2 代码案例
1 | # 查询与141号或174号员工的manager_ id和department_id相同的其他员工的employee_id,manager_id, department_id |
1 | # 返回其他job_id中比job_id为“IT_PROG”部门任一工资低的员工号、姓名、job_id、salary |
1 | # 查询平均工资最低的部门id |
3.3 空值问题
1 | # 查询出所有管理者的名称 |
4. 相关子查询
4.1 相关子查询的执行流程
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的查询就称为关联子查询。
相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询。
说明:子查询中使用主查询中的列
4.2 代码案例
查询员工工资大于本部门平均工资的员工的last_name,salary,department_id
1 | SELECT last_name,salary,department_id |
1 | # 查询员工的id,salary,按照department_name排序 |
4.3 EXISTS 和 NOT EXISTS关键字
- 关联子查询通常也会和
EXISTS操作符
来一起使用,用来检查在子查询中是否存在满足条件的行。 - 如果子查询中不存在满足条件的行:
- 条件返回false
- 继续在子查询中查找
- 如果在子查询中找到满足条件的行:
- 不在子查询中继续查找
- 条件返回true
NOT EXISTS
关键字表示如果不存在某种条件,则返回ture,否则返回false
1 | # 查询公司管理者的employee_id,last_name,job_id,department_id |
**结论:**除了GROUP BY
和LIMIT
外,其他地方都可以写子查询
单行操作符 | ANY | ALL |
---|---|---|
> ,>= |
大于任意一个,大于最小的 | 大于所有,大于最大的 |
< ,<= |
小于任意一个,小于最大的 | 小于所有,小于最小的 |
4.4 相关更新
语法:
1 | UPDATE table1 alias1 |
使用相关子查询依据一个表中的数据更新另一个表的数据。
4.5 相关删除
语法:
1 | DELETE FROM table1 alias1 |
使用相关子查询依据一个表中的数据删除另一个表的数据。
5. 课后练习
1 | #1.查询和Zlotkey相同部门的员工姓名和工资 |
创建和管理库表
1. 基础知识
1.1 标识符的命名规则
- 数据库名、表名不得超过30个字符,变量名限制为29个
- 必须只能包含A-Z、a-z、0-9、_共63个字符
- 数据库名、表名字段名等对象名中间不要包含空格
- 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
- 必须保证字段没有和保留字、数据库系统、或常用方法冲突,如果坚持使用,请在SQL语句中使用`(着重号)引起来
- 必须保证字段名和类型的一致性:在命名字段并为其指定数据类型的时候一定要保证一致性,假如数据类型在一个表里是整数,那在另一个表里别变成字符型了
1.2 MySQL中的数据类型
类型 | 类型举例 |
---|---|
整数类型 | TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT |
浮点类型 | FLOAT、DOUBLE |
定点数类型 | DECIMAL |
位类型 | BIT |
日期时间类型 | YEAR、TIME、DATE、DATETIME、TIMESTAMP |
文本字符串类型 | CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT |
枚举类型 | ENUM |
集合类型 | SET |
二进制字符串类型 | BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB |
JSON类型 | JSON对象、JSON数组 |
空间数据类型 | 单值:GEOMETRY、POINT、LINESTRING、POLYGON; 集合:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION |
其中,常用的几类类型介绍如下
数据类型 | 描述 |
---|---|
INT | 从-2^31到2^31-1的整型数据。存储大小为4个字节 |
CHAR(size) | 定长字符数据。若未指定,默认为一个字符,最大长度为255 |
VARCHAR(size) | 可变长字符数据,根据字符串实际长度保存,必须指定长度 |
FLOAT(M,D) | 单精度,占用4个字节,M=整数位+小数位,D=小数位。D<=M<=244,0<=D<=30,默认M+D<=6 |
DOUBLE(M,D) | 双精度,占用8个字节,D<=M<=255,0<=D<=30,默认M+D<=15 |
DECIMAL(M,D) | 高精度小数,占用M+2个字节,D<=M<=65,0<=D<=30,最大取值范围于DOUBLE相同 |
DATE | 日期类型数据,格式YYYY-MM-DD |
BLOB | 二进制形式的长文本数据,最大可达4G |
TEXT | 长文本数据,最大可达4G |
2. 创建和管理数据库
2.1 创建数据库
- 方式一:创建数据库
1 | CREATE DATABASE 数据库名; |
- 方式二:创建数据库并指定字符集
1 | CREATE DATABASE 数据库名 CHARACTER SET 字符集; |
- 方式三:判断数据库中是否存在,不存在则创建数据库(
推荐
)
1 | CREATE DATABASE 数据库名 IF NOT EXISTS 数据库名; |
如果MySQL中已经存在相关的数据库,则忽略创建语句,不在创建数据库。
注意:DATABASE不能改名。一些可视化工具可以改名,它是建新库,把所有表复制到新库,在删除旧库完成的
2.2 使用数据库
- 查看当前的所有数据库
1 | SHOW DATABASES; # s代表有多个数据库,复数 |
- 查询指定库下的所有表
1 | SHOW TABLES; # 查询当前库下的所有表 |
- 查询数据库创建信息
1 | SHOW CREATE DATABASE 数据库名; |
- 查看当前正在使用的数据库
1 | SELECT DATABASE() FROM DUAL; # 使用MySQL的一个全局函数 |
- 切换数据库
1 | USE 数据库名; |
注意:要操作的表格和数据之前必须先说明是对哪个数据库进行操作,否则就要对所有对象加上”数据库名”。
2.3 修改数据库
- 更改数据库字符集
1 | ALTER DATABASE 数据库名 CHARACTER SET 字符集; # 比如jbk,utf8等。 |
2.4 删除数据库
- 方式一
1 | DROP DATABASE 数据库名; |
- 方式二(推荐)
1 | DROP DATABASE IF EXISTS 数据库名; |
3. 创建和管理表
3.1 创建表
3.1.1 创建方式一
- 必须具备
- CREATE TABLE的权限
- 存储空间
- 语法格式
1 | CREATE TABLE [IF NOT EXISTS] 表名( |
加上了IF NOT EXISTS关键字,则表示:如果当前数据库中不存在要创建的数据表,则创建数据表; 如果当前数据库中已经存在要创建的数据表,则忽略建表语句,不再创建数据表。
- 案例
1 | CREATE TABLE IF NOT EXISTS myemp1( |
3.1.2 创建方式二
- 使用 AS subquery 选项,将创建表和插入数据结合起来
- 指定的列和子查询中的列要一一对应
- 通过列名和默认值定义列
- 语法
1 | CREATE TABLE table |
- 举例
1 | CREATE TABLE emp1 AS SELECT * FROM employees; # 相当于复制employees表,名称叫emp1 |
1 | # 此时的查询语句可以结构比较丰富,使用前面学到的各种SELECT |
3.1.3 查看数据表结构
在MySQL中创建好数据表之后,可以查看数据表的结构。MySQL支持使用 DESCRIBE/DESC
语句查看数据表结构,也支持使用SHOW CREATE TABLE
语句查看数据表结构。
语法格式如下:
1 | SHOW CREATE TABLE 表名; |
使用SHOW CREATE TABLE
语句不仅可以查看表创建时的详细语句,还可以查看存储引擎和字符编码。
3.2 修改表
修改表指的是修改数据库中已经存在的数据表的结构。
使用 ALTER TABLE 语句可以实现:
- 向已有的表中添加字段
- 修改现有表中的字段
- 删除现有表中的字段
- 重命名现有表中的字段名
3.2.1 添加一个字段
- 语法
1 | ALTER TABLE 表名 ADD [COLUMN] 字段名 字段类型 [FIRST|AFTER 字段名]; |
- 举例
1 | ALTER TABLE dept80 ADD job_id varchar(15); # 默认将字段追加到表的最后位置 |
3.2.2 修改字段
可以修改列的数据类型,长度、默认值和位置
修改字段数据类型、长度、默认值、位置的语法格式如下:
1 | ALTER TABLE 表名 MODIFY [COLUMN] 字段名1 字段类型 [DEFAULT 默认值] [FIRST|AFTER 字段名2]; |
- 举例
1 | ALTER TABLE dept80 MODIFY last_name VARCHAR(30); |
- 对默认值的修改只影响今后对表的修改
- 此外,还可以通过此种方式修改列的约束。这里暂先不讲。
3.2.3 重命名一个字段
- 语法
1 | ALTER TABLE 表名 CHANGE [column] 列名 新列名 新数据类型; |
- 举例
1 | ALTER TABLE dept80 CHANGE department_name dept_name varchar(15); |
3.2.4 删除一个字段
- 语法
1 | ALTER TABLE 表名 DROP [COLUMN] 字段名; |
- 举例
1 | ALTER TABLE dept80 DROP COLUMN job_id; |
3.3 重命名表
- 方式一:使用RENAME
1 | RENAME TABLE emp |
- 方式二:
1 | ALTER table dept |
- 必须是对象的拥有者
3.4 删除表
- 在MySQL中,当一张数据表 没有与其他任何数据表形成关联关系 时,可以将当前数据表直接删除。
- 数据和结构都被删除
- 所有正在运行的相关事务被提交
- 所有相关索引被删除
- 语法格式:
1 | DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, …, 数据表n]; |
IF EXISTS 的含义为:如果当前数据库中存在相应的数据表,则删除数据表;如果当前数据库中不存 在相应的数据表,则忽略删除语句,不再执行删除数据表的操作。
- 举例:
1 | DROP TABLE dept80; |
- DROP TABLE 语句不能回滚
3.5 清空表
表示清空表中的所有数据,但是表结构保留
- TRUNCATE TABLE语句:
- 删除表中所有的数据
- 释放表的存储空间
- 举例:
1 | TRUNCATE TABLE detail_dept; |
- TRUNCATE语句不能回滚,而使用 DELETE 语句删除数据,可以回滚
阿里开发规范:
【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无 事务且不触发 TRIGGER,有可能造成事故,故不建议在开发代码中使用此语句。
说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
4. (事务)COMMIT 和 ROLLBACK
COMMIT
:提交数据。一旦执行COMMIT,则数据就被永久保存在数据库中,意味着数据不可以回滚
ROLLBACK
:回滚数据。一旦执行ROLLBACK,则可以实现数据的回滚。回滚到最近一次COMMIT之后。
对比
TRUNCATE TABLE
和DELETE FROM
相同点
都可以实现对表中所有数据的删除,同时保留表结构
不同点
TRUNCATE TABLE
一旦执行此操作,表中数据全部清除。同时数据是不可以回滚的。DELETE FROM
一旦执行此操作,表数据可以全部清除(不带WHERE)。同时数据是可以回滚的。同时,数据是可以回滚的。
DML 和 DDL的说明
- DDL操作一旦执行就不可以回滚,指令
SET AUTOCOMMIT = FALSE
对DDL失效 - DML默认情况下,一旦执行,也是不可以回滚的。但是如果在执行DML之前执行了
SET AUTOCOMMIT = FALSE
(设置不自动提交),则执行的DML就可以实现回滚。
- DDL操作一旦执行就不可以回滚,指令
1 | SET AUTOCOMMIT = FALSE; |
5. 课后练习
练习一
1 | #1. 创建数据库test01_office,指明字符集为utf8。并在此数据库下执行下述操作 |
练习二
1 | # 1、创建数据库 test02_market |
字段名 | 数据类型 |
---|---|
c_num | int |
c_name | varchar(50) |
c_contact | varchar(50) |
c_city | varchar(50) |
c_birth | date |
1 | # 3、将 c_contact 字段移动到 c_birth 字段后面 |
练习三
1 | # 1、创建数据库test03_company |
字段名 | 数据类型 |
---|---|
officeCode | int |
city | varchar(30) |
address | varchar(50) |
country | varchar(50) |
postalCode | varchar(25) |
1 | # 3、创建表employees |
字段名 | 数据类型 |
---|---|
empNum | int |
lastName | varchar(50) |
firstName | varchar(50) |
mobile | varchar(25) |
code | int |
jobTitle | varchar(50) |
birth | date |
note | varchar(255) |
sex | varchar(5) |
1 | # 4、将表employees的mobile字段修改到code字段后面 |
数据处理之增删改
1. 插入数据
1.1 方式一:Value方式添加
情况1:为表的所有字段按默认的顺序插入
- 语法
1 | INSERT INTO 表名 VALUE(value1,value2,value3,...); |
值列表中需要为表的每一个字段指定值,并且值的顺序必须和数据表中字段定义时的顺序相同。
- 举例
1 | # 准备表 |
1 | INSERT INTO emp01 VALUES(1,'Tom',now()); |
情况2:为表指定字段插入数据
- 语法
1 | INSERT INTO 表名(column1 [, column2, …, columnn]) |
为表的指定字段插入数据,就是在INSERT语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值。
在 INSERT 子句中随意列出列名,但是一旦列出,VALUES中要插入的value1,….valuen需要与column1,…columnn列一一对应。如果类型不同,将无法插入,并且MySQL会产生错误。
- 举例
1 | INSERT INTO emp01(id,`name`,hire_date) VALUES(2,'zhuli',now()); |
情况3:同时插入多条记录
- 语法
1 | INSERT INTO table_name |
INSERT语句可以同时向数据表中插入多条记录,插入时指定多个值列表,每个值列表之间用逗号分隔开。
- 举例
1 | INSERT INTO emp01(id,`name`,hire_date) |
小结:
VALUES
也可以写成VALUE
,但是VALUES是标准写法。
1.2 方式二:将查询结果插入到表中
INSERT还可以将SELECT语句查询的结果插入到表中,此时不需要把每一条记录的值一个一个输入,只需要使用一条INSERT语句和一条SELECT语句组成的组合语句即可快速地从一个或多个表中向一个表中插入多行。
- 语法
1 | INSERT INTO 目标表名 |
在 INSERT 语句中加入子查询
不必书写 VALUES 子句。
子查询中的值列表应与 INSERT 子句中的列名对应。
- 举例
1 | INSERT INTO emp01 |
2. 更新数据
- 语法
1 | UPDATE table_name |
可以一次更新多条数据。
如果需要回滚数据,需要保证在DML前,进行设置:SET AUTOCOMMIT = FALSE;
- 使用 WHERE 子句指定需要更新的数据。
1 | UPDATE emp01 set `name`='bobo' WHERE id = 1; |
- **注意:**如果省略 WHERE 子句,则表中的所有数据都将被更新。
3. 删除数据
- 语法
1 | DELETE FROM table_name [WHERE <condition>]; |
able_name指定要执行删除操作的表;“[WHERE ]”为可选参数,指定删除条件,如果没有WHERE子句,
DELETE语句将删除表中的所有记录。
- 使用 WHERE 子句删除指定的记录。
1 | DELETE FROM emp01 WHERE id = 1; |
- 如果省略 WHERE 子句,则表中的全部数据将被删除
4. 计算列
什么叫计算列呢?简单来说就是某一列的值是通过别的列计算得来的。例如,a列值为1、b列值为2,c列不需要手动插入,定义a+b的结果为c的值,那么c就是计算列,是通过别的列计算得来的。
在MySQL 8.0中,CREATE TABLE 和 ALTER TABLE 中都支持增加计算列。下面以CREATE TABLE为例进行讲解。
举例:定义数据表tb1,然后定义字段id、字段a、字段b和字段c,其中字段c为计算列,用于计算a+b的值。 首先创建测试表tb1,语句如下:
1 | CREATE TABLE tb1( |
1 | INSERT INTO VALUES(1,20,30); # a=20,b=30,c=50 |
4. 课后练习
练习一
1 | # 1. 创建数据库dbtest11 |
练习二
1 | # 1. 使用现有数据库dbtest11 |
字段名 | 字段说明 | 数据类型 |
---|---|---|
name | 宠物名称 | VARCHAR(20) |
owner | 宠物主人 | VARCHAR(20) |
species | 种类 | VARCHAR(20) |
sex | 性别 | CHAR(1) |
birth | 出生日期 | YEAR |
death | 死亡日期 | YEAR |
1 | # 3. 添加记录 |
name | owner | species | sex | birth | death |
---|---|---|---|---|---|
Fluffy | harold | Cat | f | 2003 | 2010 |
Claws | gwen | Cat | m | 2004 | |
Buffy | Dog | f | 2009 | ||
Fang | benny | Dog | m | 2000 | |
bowser | diane | Dog | m | 2003 | 2009 |
Chirpy | Bird | f | 2008 |
1 | # 4. 添加字段:主人的生日owner_birth DATE类型 |
练习三
1 | # 1. 使用dbtest11数据库 |
id | name | sex | tel | addr | salary |
---|---|---|---|---|---|
10001 | 张一一 | 男 | 13456789000 | 山东青岛 | 1001.58 |
10002 | 刘小红 | 女 | 13454319000 | 河北保定 | 1201.21 |
10003 | 李四 | 男 | 0751-1234567 | 广东佛山 | 1004.11 |
10004 | 刘小强 | 男 | 0755-5555555 | 广东深圳 | 1501.23 |
10005 | 王艳 | 女 | 020-1232133 | 广东广州 | 1405.16 |
1 | # 3. 查询出薪资在1200~1300之间的员工信息 |
5. 综合案例
1 | # 1、创建数据库test01_library |
字段名 | 字段说明 | 数据类型 |
---|---|---|
id | 书编号 | INT |
name | 书名 | VARCHAR(50) |
authors | 作者 | VARCHAR(100) |
price | 价格 | FLOAT |
pubdate | 出版日期 | YEAR |
note | 说明 | VARCHAR(100) |
num | 库存 | INT |
1 | # 3、向books表中插入记录 |
id | name | authors | price | pubdate | note | num |
---|---|---|---|---|---|---|
1 | Tal of AAA | Dickes | 23 | 1995 | novel | 11 |
2 | EmmaT | Jane lura | 35 | 1993 | joke | 22 |
3 | Story of Jane | Jane Tim | 40 | 2001 | novel | 0 |
4 | Lovey Day | George Byron | 20 | 2005 | novel | 30 |
5 | Old land | Honore Blade | 30 | 2010 | law | 0 |
6 | The Battle | Upton Sara | 30 | 1999 | medicine | 40 |
7 | Rose Hood | Richard haggard | 28 | 2008 | cartoon | 28 |
1 | # 4、将小说类型(novel)的书的价格都增加5。 |
MySQL数据类型
1. MySQL中的数据类型
类型 | 类型举例 |
---|---|
整数类型 | TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT |
浮点类型 | FLOAT、DOUBLE |
定点数类型 | DECIMAL |
位类型 | BIT |
日期时间类型 | YEAR、TIME、DATE、DATETIME、TIMESTAMP |
文本字符串类型 | CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT |
枚举类型 | ENUM |
集合类型 | SET |
二进制字符串类型 | BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB |
JSON类型 | JSON对象、JSON数组 |
空间数据类型 | 单值:GEOMETRY、POINT、LINESTRING、POLYGON; 集合:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION |
常用数据类型的属性,如下:
MySQL关键字 | 含义 |
---|---|
NULL | 数据列可包含NULL值 |
NOT NULL | 数据列不允许包含NULL值 |
DEFAULT | 默认值 |
PRIMARY KEY | 主键 |
AUTO_INCREMENT | 自动递增,适用于整数类型 |
UNSIGNED | 无符号 |
CHARACTER SET name | 指定一个字符集 |
2. 整数类型
2.1 类型介绍
整数类型一共包括五种TINYINT、SMALLINT、MEDIUMNT、INT(INTEGER)和BIGNT。
他们的区别如下表所示
整数类型 | 字节 | 有符号数取值范围 | 无符号数取值范围 |
---|---|---|---|
TINYINT | 1 | -128~127 | 0~255 |
SMALLINT | 2 | -32768~32767 | 0~65535 |
MEDIUMINT | 3 | -8388608~8388607 | 0~16777215 |
INT、INTEGER | 4 | -2147483648~2147483647 | 0~4294967295 |
BIGINT | 8 | 9223372036854775808~9223372036854775807 | 0~18446744073709551615 |
2.2 可选属性
整数类型的可选属性有三个:
M
M
: 表示显示宽度,M的取值范围是(0, 255)。例如,int(5):当数据宽度小于5位的时候在数字前面需要用 字符填满宽度。该项功能需要配合ZEROFILL
使用,表示用“0”填满宽度,否则指定显示宽度无效。如果设置了显示宽度,那么插入的数据宽度超过显示宽度限制,会不会截断或插入失败?
答案:不会对插入的数据有任何影响,还是按照类型的实际宽度进行保存,即显示宽度与类型可以存储的值范围无关 。从MySQL 8.0.17开始,整数数据类型不推荐使用显示宽度属性。
整型数据类型可以在定义表结构时指定所需要的显示宽度,如果不指定,则系统为每一种类型指定默认 的宽度值。
UNSIGNED
UNSIGNED
: 无符号类型(非负),所有的整数类型都有一个可选的属性UNSIGNED
(无符号属性),无 符号整数类型的最小取值为0。所以,如果需要在MySQL数据库中保存非负整数值时,可以将整数类型设 置为无符号类型。int类型默认显示宽度为int(11),无符号int类型默认显示宽度为int(10)。
ZEROFILL
ZEROFILL : 0填充,(如果某列是ZEROFILL,那么MySQL会自动为当前列添加UNSIGNED属性),如果指 定了ZEROFILL只是表示不够M位时,用0在左边填充,如果超过M位,只要不超过数据存储范围即可。
原来,在 int(M) 中,M 的值跟 int(M) 所占多少存储空间并无任何关系。 int(3)、int(4)、int(8) 在磁盘上都 是占用 4 bytes 的存储空间。也就是说,int(M),必须和UNSIGNED ZEROFILL一起使用才有意义。如果整 数值超过M位,就按照实际位数存储。只是无须再用字符 0 进行填充。
2.3 适用场景
TINYINT
:一般用于枚举数据,比如系统设定取值范围很小且固定的场景。
SMALLINT
:可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等
MEDIUMINT
:用于较大整数的计算,比如车站每日的客流量等。
INT、INTEGER
:取值范围足够大,一般情况下不用考虑超限问题,用得最多。比如商品编号。
BIGINT
:只有当你处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、证 券公司衍生产品持仓等。
3. 浮点类型
浮点数和定点数类型的特点是可以 处理小数 ,你可以把整数看成小数的一个特例。因此,浮点数和定点数的使用场景,比整数大多了。 MySQL支持的浮点数类型,分别是 FLOAT、DOUBLE、REAL。
- FLOAT 表示单精度浮点数;
- DOUBLE 表示双精度浮点数;
REAL默认就是 DOUBLE。如果你把 SQL 模式设定为启用“ REAL_AS_FLOAT ”,那 么,MySQL 就认为REAL 是 FLOAT。如果要启用“
REAL_AS_FLOAT
”,可以通过以下 SQL 语句实现:\1
SET sql_mode = “REAL_AS_FLOAT”;
**问题1:**FLOAT 和 DOUBLE 这两种数据类型的区别是啥呢?
FLOAT 占用字节数少,取值范围小;DOUBLE 占用字节数多,取值范围也大。
**问题2:**为什么浮点数类型的无符号数取值范围,只相当于有符号数取值范围的一半,也就是只相当于有符号数取值范围大于等于零的部分呢?
MySQL 存储浮点数的格式为: 符号(S) 、 尾数(M) 和 阶码(E) 。因此,无论有没有符号,MySQL 的浮点数都会存储表示符号的部分。因此, 所谓的无符号数取值范围,其实就是有符号数取值范围大于等于零的部分。
注意:
从MySQL 8.0.17开始,
FLOAT(M,D)
和DOUBLE(M,D)
用法在官方文档中已经明确不推荐使用,将来可能被移除。另外,关于浮点型FLOAT和DOUBLE的UNSIGNED也不推荐使用了,将来也可能被移除。推荐使用DECIMAL
。
4. 定点数类型
MySQL中的定点数类型只有 DECIMAL 一种类型。
数据类型 字节数 含义 DECIMAL(M,D),DEC,NUMERIC M+2字节 有效范围由M和D决定 使用 DECIMAL(M,D) 的方式表示高精度小数。其中,M被称为精度,D被称为标度。0<=M<=65, 0<=D<=30,D<M。例如,定义DECIMAL(5,2)的类型,表示该列取值范围是-999.99~999.99。
DECIMAL(M,D)的最大取值范围与DOUBLE类型一样,但是有效的数据范围是由M和D决定的。DECIMAL 的存储空间并不是固定的,由精度值M决定,总共占用的存储空间为M+2个字节。也就是说,在一些对精度要求不高的场景下,比起占用同样字节长度的定点数,浮点数表达的数值范围可以更大一些。
定点数在MySQL内部是以
字符串
的形式进行存储,这就决定了它一定是精准的。当DECIMAL类型不指定精度和标度时,其默认为DECIMAL(10,0)。当数据的精度超出了定点数类型的精度范围时,则MySQL同样会进行四舍五入处理
5. 位类型:BIT
BIT类型中存储的是二进制值,类似010110。
二进制字符串类型 | 长度 | 长度范围 | 占用空间 |
---|---|---|---|
BIT(M) | M | 1 <= M <= 64 | 约为(M + 7)/8个字节 |
BIT类型,如果没有指定(M),默认是1位。这个1位,表示只能存1位的二进制值。这里(M)是表示二进制的位数,位数最小值为1,最大值为64。
6. 日期和时间类型
日期与时间是重要的信息,在我们的系统中,几乎所有的数据表都用得到。原因是客户需要知道数据的时间标签,从而进行数据查询、统计和处理。
MySQL有多种表示日期和时间的数据类型,不同的版本可能有所差异,MySQL8.0版本支持的日期和时间类型主要有:YEAR类型、TIME类型、DATE类型、DATETIME类型和TIMESTAMP类型。
YEAR
类型通常用来表示年DATE
类型通常用来表示年、月、日TIME
类型通常用来表示时、分、秒DATETIME
类型通常用来表示年、月、日、时、分、秒TIMESTAMP
类型通常用来表示带时区的年、月、日、时、分、秒
类型 | 名称 | 字节 | 日期格式 | 最小值 | 最大值 |
---|---|---|---|---|---|
YEAR | 年 | 1 | YYYY或YY | 1901 | 2155 |
DATE | 日期 | 3 | YYYY-MM-DD | 1000-01-01 | 9999-12-03 |
TIME | 时间 | 3 | HH:MM:SS | -838:59:59 | 838:59:59 |
DATETIME | 日期时间 | 8 | YYYY-MM-DD HH:MM:SS |
1000-01-01 00:00:00 |
9999-12-31 23:59:59 |
TIMESTAMP | 日期时间 | 4 | YYYY-MM-DD HH:MM:SS |
1970-01-01 00:00:00 UTC |
2038-01-19 03:14:07UTC |
可以看到,不同数据类型表示的时间内容不同、取值范围不同,而且占用的字节数也不一样,你要根据实际需要灵活选取。
为什么时间类型 TIME 的取值范围不是 -23:59:59~23:59:59 呢?原因是 MySQL 设计的 TIME 类型,不光表示一天之内的时间,而且可以用来表示一个时间间隔,这个时间间隔可以超过 24 小时。
6.1 YEAR类型
YEAR类型用来表示年份,在所有的日期时间类型中所占用的存储空间最小,只需要1个字节
的存储空间。
在MySQL中,YEAR有以下几种存储格式:
以4位字符串或数字格式表示YEAR类型,其格式为YYYY,最小值为1901,最大值为2155。
以2位字符串格式表示YEAR类型,最小值为00,最大值为99。
- 当取值为01到69时,表示2001到2069;
- 当取值为70到99时,表示1970到1999;
- 当取值整数的0或00添加的话,那么是0000年;
- 当取值是日期/字符串的’0’添加的话,是2000年。
从MySQL5.5.27开始,2位格式的YEAR已经不推荐使用。YEAR默认格式就是“YYYY”,没必要写成YEAR(4),
从MySQL 8.0.19开始,不推荐使用指定显示宽度的**YEAR(4)**数据类型。
6.2 DATE类型
DATE类型表示日期,没有时间部分,格式为YYYY-MM-DD
,其中,YYYY表示年份,MM表示月份,DD表示日期。需要3个字节
的存储空间。在向DATE类型的字段插入数据时,同样需要满足一定的格式条件。
以
YYYY-MM-DD
格式或者YYYYMMDD
格式表示的字符串日期,其最小取值为1000-01-01
,最大取值为9999-12-03
。YYYYMMDD格式会被转化为YYYY-MM-DD格式。以
YY-MM-DD
格式或者YYMMDD
格式表示的字符串日期,此格式中,年份为两位数值或字符串满足YEAR类型的格式条件为:当年份取值为00到69时,会被转化为2000到2069;当年份取值为70到99时,会被转化为1970到1999。使用
CURRENT_DATE()
或者NOW()
函数,会插入当前系统的日期。
6.3 TIME类型
TIME类型用来表示时间,不包含日期部分。在MySQL中,需要3个字节
的存储空间来存储TIME类型的数据,可以使用“HH:MM:SS”格式来表示TIME类型,其中,HH表示小时,MM表示分钟,SS表示秒。
在MySQL中,向TIME类型的字段插入数据时,也可以使用几种不同的格式。
- 可以使用带有冒号的字符串,比如’
D HH:MM:SS
‘ 、’HH:MM:SS
‘、’HH:MM
‘、’D HH:MM
‘、’D HH
‘或’SS
‘格式,都能被正确地插入TIME类型的字段中。其中D表示天,其最小值为0,最大值为34。如果使用带有D格式的字符串插入TIME类型的字段时,D会被转化为小时,计算格式为D*24+HH。当使用带有冒号并且不带D的字符串表示时间时,表示当天的时间,比如12:10表示12:10:00,而不是00:12:10。 - 可以使用不带有冒号的字符串或者数字,格式为’
HHMMSS
‘或者HHMMSS
。如果插入一个不合法的字符串或者数字,MySQL在存储数据时,会将其自动转化为00:00:00进行存储。比如1210,MySQL会将最右边的两位解析成秒,表示00:12:10,而不是12:10:00。 - 使用
CURRENT_TIME()
或者NOW()
,会插入当前系统的时间。
6.4 DATETIME类型
DATETIME类型在所有的日期时间类型中占用的存储空间最大,总共需要8个字节
的存储空间。在格式上为DATE类型和TIME类型的组合,可以表示为 YYYY-MM-DD HH:MM:SS ,其中YYYY表示年份,MM表示月份,DD表示日期,HH表示小时,MM表示分钟,SS表示秒。
在向DATETIME类型的字段插入数据时,同样需要满足一定的格式条件。
以
YYYY-MM-DD HH:MM:SS
格式或者YYYYMMDDHHMMSS
格式的字符串插入DATETIME类型的字段时,最小值为1000-01-01 00:00:00,最大值为9999-12-03 23:59:59。- 以YYYYMMDDHHMMSS格式的数字插入DATETIME类型的字段时,会被转化为YYYY-MM-DD HH:MM:SS格式。
以
YY-MM-DD HH:MM:SS
格式或者YYMMDDHHMMSS
格式的字符串插入DATETIME类型的字段时,两位数的年份规则符合YEAR类型的规则,00到69表示2000到2069;70到99表示1970到1999。使用函数
CURRENT_TIMESTAMP()
和NOW()
,可以向DATETIME类型的字段插入系统的当前日期和时间。
6.5 TIMESTAMP类型
TIMESTAMP类型也可以表示日期时间,其显示格式与DATETIME类型相同,都是YYYY-MM-DD HH:MM:SS
,需要4个字节的存储空间。但是TIMESTAMP存储的时间范围比DATETIME要小很多,只能存储“1970-01-01 00:00:01 UTC”到“2038-01-19 03:14:07 UTC”之间的时间。其中,UTC表示世界统一时间,也叫作世界标准时间。
- 存储数据的时候需要对当前时间所在的时区进行转换,查询数据的时候再将时间转换回当前的时区。因此,使用TIMESTAMP存储的同一个时间值,在不同的时区查询时会显示不同的时间。
向TIMESTAMP类型的字段插入数据时,当插入的数据格式满足YY-MM-DD HH:MM:SS和YYMMDDHHMMSS时,两位数值的年份同样符合YEAR类型的规则条件,只不过表示的时间范围要小很多。
TIMESTAMP和DATETIME的区别:
- TIMESTAMP存储空间比较小,表示的日期时间范围也比较小
- 底层存储方式不同,TIMESTAMP底层存储的是毫秒值,距离1970-1-1 0:0:0 0毫秒的毫秒值。
- 两个日期比较大小或日期计算时,TIMESTAMP更方便、更快。
- TIMESTAMP和时区有关。TIMESTAMP会根据用户的时区不同,显示不同的结果。而DATETIME则只能反映出插入时当地的时区,其他时区的人查看数据必然会有误差的。
6.6 开发中的经验
用得最多的日期时间类型,就是 DATETIME
。虽然 MySQL 也支持 YEAR(年)、 TIME(时间)、DATE(日期),以及 TIMESTAMP 类型,但是在实际项目中,尽量用 DATETIME 类型。因为这个数据类型包括了完整的日期和时间信息,取值范围也最大,使用起来比较方便。毕竟,如果日期时间信息分散在好几个字段,很不容易记,而且查询的时候,SQL 语句也会更加复杂。
此外,一般存注册时间、商品发布时间等,不建议使用DATETIME存储,而是使用 ·时间戳
,因为DATETIME虽然直观,但不便于计算。
1 | # 获取当前时间戳 |
7. 文本字符串
在实际的项目中,我们还经常遇到一种数据,就是字符串数据。
MySQL中,文本字符串总体上分为 CHAR
、 VARCHAR
、TINYTEXT
、 TEXT
、 MEDIUMTEXT
、 LONGTEXT
、 ENUM
、 SET
等类型。
7.1 CHAR与VARCHAR类型
CHAR和VARCHAR类型都可以存储比较短的字符串
字符串(文本类型) | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
CHAR(M) | 固定长度 | M | 0 <= M <= 255 | M个字节 |
VARCHAR(M) | 可变长度 | M | 0 <= M <= 65535 | (实际长度 + 1) 个字节 |
CHAR类型:
- CHAR(M) 类型一般需要预先定义字符串长度。如果不指定(M),则表示长度默认是1个字符。
- 如果保存时,数据的实际长度比CHAR类型声明的长度小,则会在
右侧填充
空格以达到指定的长度。当MySQL检索CHAR类型的数据时,CHAR类型的字段会去除尾部的空格。 - 定义CHAR类型字段时,声明的字段长度即为CHAR类型字段所占的存储空间的字节数。
VARCHAR类型:
VARCHAR(M) 定义时, 必须指定
长度M
,否则报错。MySQL4.0版本以下,varchar(20):指的是20字节,如果存放UTF8汉字时,只能存6个(每个汉字3字节) ;MySQL5.0版本以上,varchar(20):指的是20字符。
检索VARCHAR类型的字段数据时,会保留数据尾部的空格。VARCHAR类型的字段所占用的存储空间为字符串实际长度加1个字节。
哪些情况使用 CHAR 或 VARCHAR更好
类型 | 特点 | 空间上 | 时间上 | 适用场景 |
---|---|---|---|---|
CHAR(M) | 固定长度 | 浪费存储空间 | 效率高 | 存储不大,速度要求高 |
VARCHAR(M) | 可变长度 | 节省存储空间 | 效率低 | 非CHAR的情况 |
情况1:存储很短的信息。比如门牌号码101,201……这样很短的信息应该用char,因为varchar还要占个byte用于存储信息长度,本来打算节约存储的,结果得不偿失。
情况2:固定长度的。比如使用uuid作为主键,那用char应该更合适。因为他固定长度,varchar动态根据长度的特性就消失了,而且还要占个长度信息。
情况3:十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的。
情况4:具体存储引擎中的情况:
MyISAM
数据存储引擎和数据列:MyISAM数据表,最好使用固定长度(CHAR)的数据列代替可变长度(VARCHAR)的数据列。这样使得整个表静态化,从而使 数据检索更快 ,用空间换时间。MEMORY
存储引擎和数据列:MEMORY数据表目前都使用固定长度的数据行存储,因此无论使用CHAR或VARCHAR列都没有关系,两者都是作为CHAR类型处理的。InnoDB 存储引擎,建议使用VARCHAR类型。因为对于InnoDB数据表,内部的行存储格式并没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),而且主要影响性能的因素是数据行使用的存储总量,由于char平均占用的空间多于varchar,所以除了简短并且固定长度的,其他考虑varchar。这样节省空间,对磁盘I/O和数据存储总量比较好。
7.2 TEXT类型
在MySQL中,TEXT用来保存文本类型的字符串,总共包含4种类型,分别为TINYTEXT、TEXT、 MEDIUMTEXT 和 LONGTEXT 类型。
在向TEXT类型的字段保存和查询数据时,系统自动按照实际长度存储,不需要预先定义长度。这一点和VARCHAR类型相同。
每种TEXT类型保存的数据长度和所占用的存储空间不同,如下:
文本字符串类型 | 特点 | 长度 | 长度范围 | 占用存储空间 |
---|---|---|---|---|
TINYTEXT | 小文本、可变长度 | L | 0 <= L <= 255 | L + 2 个字节 |
TEXT | 文本、可变长度 | L | 0 <= L <= 65535 | L + 2 个字节 |
MEDIUMTEXT | 中等文本、可变长度 | L | 0 <= L <= 16777215 | L + 3 个字节 |
LONGTEXT | 大文本、可变长度 | L | 0 <= L<= 4294967295(相当于4GB) | L + 4 个字节 |
由于实际存储的长度不确定,MySQL 不允许TEXT类型的字段做主键。遇到这种情况,你只能采用CHAR(M),或者 VARCHAR(M)。
**说明:**在保存和查询数据时,并没有删除TEXT类型的数据尾部的空格。
开发中经验:
TEXT文本类型,可以存比较大的文本段,搜索速度稍慢,因此如果不是特别大的内容,建议使用CHAR, VARCHAR来代替。还有TEXT类型不用加默认值,加了也没用。而且text和blob类型的数据删除后容易导致“空洞”,使得文件碎片比较多,所以频繁使用的表不建议包含TEXT类型字段,建议单独分出去,单独用一个表。
7.3 ENUM类型
ENUM类型也叫作枚举类型,ENUM类型的取值范围需要在定义字段时进行指定。设置字段值时,ENUM类型只允许从成员中选取单个值,不能一次选取多个值。
其所需要的存储空间由定义ENUM类型时指定的成员个数决定。
文本字符串类型 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|
ENUM | L | 1 <= L <= 65535 | 1或2个字节 |
- 当ENUM类型包含1~255个成员时,需要1个字节的存储空间
- 当ENUM类型包含256~65535个成员时,需要2个字节的存储空间。
- ENUM类型的成员个数的上限为65535个。
举例
1 | CREATE TABLE test_enum( |
7.4 SET类型
SET表示一个字符串对象,可以包含0个或多个成员,但成员个数的上限为64
。设置字段值时,可以取取值范围内的 0 个或多个值。
当SET类型包含的成员个数不同时,其所占用的存储空间也是不同的,具体如下:
成员个数范围(L表示实际成员个数) | 占用的存储空间 |
---|---|
1 <= L <= 8 | 1个字节 |
9 <= L <= 16 | 2个字节 |
17 <= L <= 24 | 3个字节 |
25 <= L <= 32 | 4个字节 |
25 <= L <= 32 | 8个字节 |
SET类型在存储数据时成员个数越多,其占用的存储空间越大。注意:SET类型在选取成员时,可以一次选择多个成员,这一点与ENUM类型不同。
举例
1 | CREATE TABLE test_set( s SET ('A', 'B', 'C') ); |
8. JSON 类型
JSON(JavaScript Object Notation)
是一种轻量级的 数据交换格式 。简洁和清晰的层次结构使得 JSON 成 为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效 率。JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻 松地传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。
在MySQL 5.7中,就已经支持JSON数据类型。在MySQL 8.x版本中,JSON类型提供了可以进行自动验证的 JSON文档和优化的存储结构,使得在MySQL中存储和读取JSON类型的数据更加方便和高效。 创建数据 表,表中包含一个JSON类型的字段 js 。
1 | CREATE TABLE test_json( |
约束
1. 约束的概述
1.1 为什么需要约束
数据完整性是指数据的精确性和可靠性。他是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而提出的。
为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面考虑:
实体完整性(Entity Integrity)
:例如,同一个表中,不能存在两条完全相同无法区分的记录域完整性(Domain Integrity)
:例如,年龄范围0-120,性别范围“男/女”引用完整性(Referential Integrity)
:例如,员工所在部门,在部门表中要找到这个部门用户自定义完整性(User-defined Integrity)
:例如,用户名唯一,密码不能为空等,本部门经理的工资不能高于本部门职工的平均工资的5倍。
1.2 什么是约束
约束是表级的强制规定。
可以在创建表的时规定约束(通过CREATE TABLE语句),或者在创建之后通过ALTER TABLE 语句规定约束。
1.3 约束的分类
根据约束数据列的限制,约束可分为:
- ==单列约束==:每个约束只约束一列
- ==多列约束==:每个约束可约束多列
根据约束的作用范围,约束可分为
- ==列级约束==:只能作用在一个列上,跟在列的定义后面
- ==表级约束==:可以作用在多个列上,不与列一起,而是单独定义。
位置 支持的约束类型 是否可以起约束名 列级约束 列的后面 语法都支持,但外键没有效果 不可以 表级约束 所有列的下面 默认和非空不支持,其他支持 可以 根据约束的作用,约束可分为:
- NOT NULL:非空约束,规定某个字段不能为空
- UNIQUE:唯一约束,规定某个字段在整个表中是唯一的
- PRIMARY KEY:主键(且非空唯一)约束
- FOREIGN KEY:外键约束
- CHECK:检查约束
- DEFAULT:默认值约束
注意: MySQL不支持check约束,但可以使用check约束,而没有任何效果
- 查看某个表已有的约束
1 | #information_schema数据库名(系统库) |
2. 非空约束
作用
限定某个字段的值不能为空
关键字
NOT NULL
特点
- 默认,所有的类型的值都可以是
NULL
,包括FLOAT,DOUBLE等数据类型。 - 非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空。
- 一个表可以有多个列都分别限定了非空
- 空字符串不等于NULL,0也不等于NULL
- 默认,所有的类型的值都可以是
2.1 添加非空约束
- 建表时
1 | CREATE TABLE 表名称( |
- 举例
1 | CREATE TABLE student( |
1 | insert into student values(1,'张三','13710011002','110222198912032545'); #成功 |
- 修改表时
1 | alter table 表名称 modify 字段名 数据类型 not null; |
- 举例
1 | # 如果添加失败,需要确保已经存在的数据中该字段的所有值必须不为空 |
1 | INSERT INTO student VALUES(4,'王五',null,'485930459684768495');# 失败,tel列不能为空 |
2.2 删除主键约束
1 | alter table 表名称 modify 字段名 数据类型 NULL;#去掉not null,相当于修改某个非注解字段,该字段允许为空 |
3. 唯一性约束
作用
用来限制某个字段的列/某列的值不能重复
关键字
UNIQUE
特点
- 同一个表可以有多个唯一性约束
- 唯一约束可以是某一列的值唯一,也可以是多个列组合的值唯一。
- 唯一性约束允许列值为空
- 在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同。
- MySQL会给唯一约束的列上默认创建一个唯一索引
3.1 添加唯一约束
- 建表时
1 | create table 表名称( |
- 举例
1 | CREATE TABLE test2( |
1 | INSERT INTO test2 VALUES(1,'张三','11@qq.com',1200);# 成功 |
- 建表后指定唯一键约束
1 | #字段列表中如果是一个字段,表示该列的值唯一。如果是两个或更多个字段,那么复合唯一,即多个字段的组合是唯一的 |
3.2 关于复合唯一约束
1 | create table 表名称( |
- 举例
1 | CREATE TABLE test_user( |
1 | INSERT INTO test_user VALUES(1,'李四','123456');# 成功 |
3.3 删除唯一约束
- 添加唯一性约束的列上也会自动创建唯一索引
- 删除唯一约束只能通过删除唯一索引的方式删除
- 删除时需要指定唯一约束名,唯一索引名和唯一约束名一样
- 如果创建唯一约束时未指定名称,如果是但列,默认和列名相同;如果是组合列,那么默认值和()中排第一个的列名相同。也可以自定义唯一约束名。
1 | #查看都有哪些约束 |
删除约束
1 | ALTER TABLE 表名 DROP INDEX uk_name_pwd; |
注意:可以通过
show index from
表名称; 查看表的索引
4. 主键约束
作用
用来唯一标识表中的一行记录
关键字
PRIMARY KEY
特点
- 主键约束相当于唯一约束+非空约束的组合,主键约束列不允许重复,也不允许出现空值。
- 一个表最多只能有一个主键约束,建立主键约束可以在列级别创建,也可以在表级别上创建。
- 主键约束对应着表中的一列或多列(复合主键)
- 如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
- MySQL的主键名总是PRIMARY,就算自己命名了主键约束名也没用。
- 当创建主键时,系统默认会在所在的列或组合上建立对应的主键索引(能够根据主键查询的,就根据主键查询,效率更高)。如果删除了主键约束,主键约束对应的索引就自动删除了
- 需要注意的一点是,不要修改主键字段的值。因为主键是数据记录的唯一标识,如果修改了主键的值,就有可能会破坏数据的完整性。
4.1 添加主键约束
- 建表时指定主键约束
1 | create table 表名称( |
- 举例
1 | create table temp( |
1 | insert into temp values(1,'张三');#成功 |
- 建表后指定主键约束
1 | # 字段列表可以是一个字段,也可以是多个字段,如果是多个字段的话,是复合主键 |
4.2 复合主键约束
1 | create table 表名称( |
1 | CREATE TABLE emp6( |
4.3 删除主键约束
1 | alter table 表名称 drop primary key; |
说明:删除主键约束,不需要指定主键名,因为一个表只有一个主键,删除主键约束后,非空约束还存在。
注意:在开发中,不要去删除主键约束!!!!
5. 自增列
作用
某个字段值自增
关键字
AUTO_INCREMENT
特点和要求
- 一个表最多只能有
一个
自增长列 - 当需要产生唯一标识符或顺序值时,可设置自增长
- 自增长列约束的列
必须是键列(主键列,唯一键列)
- 自增约束的列的数据类型必须是
整数类型
- 如果自增列指定了0和null,会在当前最大值的基础上自增;如果自增列手动设置了具体值,则直接赋值具体值。
- 一个表最多只能有
5.1 指定自增约束
- 建表时
1 | create table 表名称( |
- 举例
1 | create table employee( |
- 建表后
1 | alter table 表名称 modify 字段名 数据类型 auto_increment; |
5.2 删除自增约束
1 | #alter table 表名称 modify 字段名 数据类型 auto_increment;#给这个字段增加自增约束 |
6. 外键约束
作用
限定某个表的某个字段的引用完整性
比如:员工表的员工所在部门的选择,必须在部门表中能找到对应的部分。
关键字
FOREIGN KEY
主表和从表/父表和子表
主表(父表):被引用的表,被参考的表
从表(子表):引用别人的表,参考别人的表
例如:员工表的员工所在部门这个字段的值要参考部门表:部门表是主表,员工表是从表。
特点
从表的外键列,必须引用/参考主表的键(主键或唯一约束的列)
为啥?因为被依赖/ 被参考的值必须是唯一的
在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名(例如student_ibfk_1),也可以指定外键约束名。
创建(CREATE)表时就指定外键约束的话,先创建主表,在创建从表
删表时,先删从表(或先删除外键约束),在删除主表
当主表的记录被从表参照时,主表的记录将不允许删除,如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据。
在从表中指定外键约束,并且一个表可以建立多个外键约束
从表的外键列与主表被参照的列名字可以不相同,但是数据类型必须一样,逻辑意义一致。如果类型不一样,创建子表时就会出现错误“ERROR 1005 (HY000): Can’t create table’database.tablename’(errno: 150)”
当创建外键约束时,系统默认会在所在列上建立对应的普通索引。但是索引名是列名,不是外键约束的约束名(根据外键查询效率很高)
删除外键约束后,必须手动删除对应的索引
6.1 添加外键索引
- 建表时
1 | # 先创建主表,在创建从表 |
- 举例
1 | create table dept( #主表 |
建表后
一般情况下,表与表的关联都是提前设计好了的,因此,会在创建表的时候就把外键约束定义好。不过,如果需要修改表的设计(比如添加新的字段,增加新的关联关系),但没有预先定义外键约束,那么,就要用修改表的方式来补充定义。
1 | ALTER TABLE 从表名 |
- 举例
1 | ALTER TABLE emp1 |
总结:约束关系是针对双方的
- 添加了外键约束后,主表的修改和删除数据受约束
- 添加了外键约束后,从表的添加和修改数据受约束
- 在从表上建立外键,要求主表必须存在
- 删除主表时,要求从表从表先删除,或将从表中外键引用该主表的关系先删除
6.2 约束等级
Cascade
方式 :在父表上update/delete记录时,同步update/delete掉子表的匹配记录Set null
方式 :在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子 表的外键列不能为not nullNo action
方式 :如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作Restrict
方式 :同no action, 都是立即检查外键约束Set default
方式 (在可视化工具SQLyog中可能显示空白):父表有变更时,子表将外键列设置 成一个默认的值,但Innodb不能识别
6.3 删除外键约束
流程如下:
1 | # (1)第一步先查看约束名和删除外键约束 |
6.4 阿里开发规范
【 强制 】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学 生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发
,不适合分布式
、高并发集群
;级联更新是强阻塞,存在数据库更新风暴
的风险;外键影响 数据库的插入速度
。
7 CHECK约束
作用
检查某个字段的值是否符合xx要求,一般指的是指的范围
关键字
CHECK
说明:MYSQL 5.7 不支持
演示
1 | create table employee( |
1 | insert into employee values(1,'张三','妖'); |
8. DEFAULT约束
作用
给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显式赋值,则赋值为默认值。
关键字
DEFAULT
8.1 给字段加默认值
- 建表时
1 | create table 表名称( |
- 建表后
1 | alter table 表名称 modify 字段名 数据类型 default 默认值; |
8.2 删除默认值约束
1 | alter table 表名称 modify 字段名 数据类型 ;# 删除默认值约束,也不保留非空约束 |
9. 加强记忆
- 唯一约束、主键约束(都涉及到索引)
1 | # 唯一约束 |
- 非空约束,自增列,DEFAULT约束
1 | # 非空约束 |
视图
1. 常见的数据库对象
对象 | 描述 |
---|---|
表(TABLE) | 表是存储数据的逻辑单元,以行和列的形式存在,列就是字段,行就是记录 |
数据字典 | 就是系统表,存放数据库相关信息的表。系统表的数据通常由数据库系统维护,程序员通常不应该修改,只可查看。 |
约束(CONSTRAINT) | 执行数据校验的规则,用于保证数据完整性的规则 |
视图(VIEW) | 一个或者多个数据表里的数据的逻辑显示,视图并不存储数据 |
索引(INDEX) | 用于提供查询性能,相当于书的目录 |
存储过程(PROCEDURE) | 用于完成一次完整的业务处理,没有返回值,但可通过传出参数将多个值传给调用环境 |
存储函数(FUNCTION) | 用于完成一次特定的计算,具有一个返回值 |
触发器(TRIGGER) | 相当于一个事件监听器,当数据库发生特定事件后,触发器被处罚,完成相应的处理。 |
2. 视图的概述
2.1 为什么使用视图
视图一方面可以帮我们使用表的一部分而不是所有的表,另一方面也可以针对不同的用户指定不同的查询视图。
2.2 视图的理解
视图是一种
虚拟表
,本身是不具有数据
的,占用很少的内存空间,它是SQL中的一个重要概念。视图建立在已有表的基础上,视图赖以建立的这些表称为
基表
。视图的创建和删除只影响视图本身,不影响对应的基表。但是当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生编号,反之亦然。
向视图提供数据内容的语句为SELECT语句,可以将视图理解为存储起来的SELECT语句。
视图,向用户提供基表数据的另一种表现形式。通常情况下,小型项目的数据库可以不使用视图,但是在大型项目中,以及数据表比较复杂的情况下,视图的价值就凸显出来了,它可以帮助我们把经常查询的结果集放到虚拟表中,提升使用效率。理解和使用起来都非常方便。
3. 创建视图
- 在
CREATE VIEW
语句中嵌入子查询
1 | CREATE [OR REPLACE] |
- 精简
1 | CREATE VIEW 视图名称 |
3.1 创建单表视图
举例:
1 | CREATE VIEW empvu80 |
查询视图:
1 | SELECT * FROM empvu80; |
举例:
1 | CREATE VIEW emp_year_salary (ename,year_salary) |
举例:
1 | CREATE VIEW salvu50 |
说明1:实际上就是我们在 SQL 查询语句的基础上封装了视图 VIEW,这样就会基于 SQL 语句的结果集形 成一张虚拟表。
说明2:在创建视图时,没有在视图名后面指定字段列表,则视图中字段列表默认和SELECT语句中的字段列表一致。如果SELECT语句中给字段取了别名,那么视图中的字段名和别名相同。
3.2 创建多表联合视图
1 | CREATE VIEW dept_sum_vu |
3.3 查看视图
- 语法1:查看数据库的表对象、视图对象
1 | SHOW TABLES; |
- 语法2:查看视图的结构
1 | DESC / DESCRIBE 视图名称; |
- 语法3:查看视图的属性信息
1 | # 查看视图信息(显示数据表的存储引擎、版本、数据行数和数据大小等) |
- 语法4:查看视图的详细定义信息
1 | SHOW CREATE VIEW 视图名称; |
4. 更新视图的数据
MySQL支持使用INSERT、UPDATE和DELETE语句对视图中的数据进行插入、更新和删除操作。当视图中的 数据发生变化时,数据表中的数据也会发生变化,反之亦然。
4.1 不可更新的视图
要使视图可更新,视图中的行和底层基本表中的行之间必须存在 一对一 的关系。另外当视图定义出现如下情况时,视图不支持更新操作:
在定义视图的时候指定了
“ALGORITHM = TEMPTABLE”
,视图将不支持INSERT和DELETE操作;视图中不包含基表中所有被定义为非空又未指定默认值的列,视图将不支持INSERT操作;
在定义视图的SELECT语句中使用了
JOIN联合查询
,视图将不支持INSERT和DELETE操作;在定义视图的SELECT语句后的字段列表中使用了
数学表达式
或子查询
,视图将不支持INSERT,也 不支持UPDATE使用了数学表达式、子查询的字段值;在定义视图的SELECT语句后的字段列表中使用
DISTINCT
、聚合函数
、GROUP BY
、HAVING
、UNION
等,视图将不支持INSERT、UPDATE、DELETE;在定义视图的SELECT语句中包含了子查询,而子查询中引用了FROM后面的表,视图将不支持 INSERT、UPDATE、DELETE;
视图定义基于一个 不可更新视图 ;
常量视图。
虽然可以更新视图数据,但总的来说,视图作为
虚拟表
,主要用于方便查询
,不建议更新视图的 数据。对视图数据的更改,都是通过对实际数据表里数据的操作来完成的。
5. 修改、删除视图
5.1 修改视图
方式1:使用CREATE OR REPLACE VIEW
子句修改视图
1 | CREATE OR REPLACE VIEW empvu80 |
说明:CREATE VIEW 子句中各列的别名应和子查询中各列相对应。
方式二:ALTER VIEW
1 | ALTER VIEW 视图名称 AS 查询语句 |
5.2 删除视图
除视图只是删除视图的定义,并不会删除基表的数据。
1 | DROP VIEW IF EXISTS 视图名称; |
6. 总结
6.1 视图的优点
操作简单
将经常使用的查询操作定义为视图,可以使开发人员不需要关心视图对应的数据表的结构、表与表之间的关联关系,也不需要关心数据表之间的业务逻辑和查询条件,而只需要简单地操作视图即可,极大简 化了开发人员对数据库的操作。
减少数据冗余
视图跟实际数据表不一样,它存储的是查询语句。所以,在使用的时候,我们要通过定义视图的查询语句来获取结果集。而视图本身不存储数据,不占用数据存储的资源,减少了数据冗余。
数据安全
MySQL将用户对数据的
访问限制
在某些数据的结果集上,而这些数据的结果集可以使用视图来实现。用 户不必直接查询或操作数据表。这也可以理解为视图具有隔离性
。视图相当于在用户和实际的数据表之 间加了一层虚拟表。同时,MySQL可以根据权限将用户对数据的访问限制在某些视图上,用户不需要查询数据表,可以直接通过视图获取数据表中的信息。这在一定程度上保障了数据表中数据的安全性。
适应灵活多变的需求
当业务系统的需求发生变化后,如果需要改动数据表的结构,则工作量相对较 大,可以使用视图来减少改动的工作量。这种方式在实际工作中使用得比较多。
能够分解复杂的查询逻辑
数据库中如果存在复杂的查询逻辑,则可以将问题进行分解,创建多个视图获取数据,再将创建的多个视图结合起来,完成复杂的查询逻辑。
6.2 视图的缺点
如果我们在实际数据表的基础上创建了视图,那么,如果实际数据表的结构变更了,我们就需要及时对 相关的视图进行相应的维护。特别是嵌套的视图(就是在视图的基础上创建视图),维护会变得比较复 杂, 可读性不好 ,容易变成系统的潜在隐患。因为创建视图的 SQL 查询可能会对字段重命名,也可能包 含复杂的逻辑,这些都会增加维护的成本。
实际项目中,如果视图过多,会导致数据库维护成本的问题。
存储过程与函数
1.存储过程概述
1.1 理解
含义:就是一组经过预先编译
的SQL语句的封装。
执行过程:存储过程预先存储在Mysql服务器上,需要执行的时候,客户端只需要向服务器发出调用存储过程的命令,服务器端就可以把预先存储好的这一系列SQL语句全部执行。
好处:
- 简化操作,提高sql语句的重用性,减少开发程序员的压力
- 减少操作过程中的失误,提高效率
- 减少网络传输量(客户端不需要把所有的SQL语句通过网络发给服务器)
- 减少SQL语句暴露在网上的风险,也提高了数据查询的安全性
和视图、函数的对比:
它和视图有着同样的优点,清晰、安全、还可以减少网络传输量。不过它和视图不同,视图是虚拟表,通常不对底层数据表直接操作,而存储过程是程序化的SQL,可以直接操作底层数据表,相比于面向集合的操作方式,能够实现一些更复杂的数据处理。
一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。相较于函数,存储过程是没有返回值
的。
1.2 分类
存储过程的参数类型可以是IN、OUT和INOUT。根据这点分类如下:
- 没有参数(没有返回值)
- 仅仅带IN类型(有参数无返回)
- 仅仅带OUT类型(无参数有返回)
- 即带IN,又带OUT(有参数有返回)
- 带INOUT(有参数有返回)
注意:IN、OUT、INOUT都可以在一个存储过程中带多个。
2. 创建存储过程
2.1 语法分析
1 | CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...) |
类似于JAVA中的方法
1 | 修饰符 返回类型 方法名(参数类型 参数名){ |
说明:
1、参数前面的符号的意思
IN
:当前参数为输入参数,也就是表示入参;存储过程只是读取这个参数的值。如果没有定义参数种类,默认就是IN,表示输入参数。“OUT
:当前参数为输出参数,也就是表示出参;执行完成之后,调用这个存储过程的客户端或者应用程序就可以读取这个参数返回的值了。INOUT
:当前参数既可以为输入参数,也可以为输出参数。
2、形参类型可以是MySQL数据库中的任意类型。
3、characteristics
表示创建存储过程时指定的对存储过程的约束条件,其取值信息如下:
1 | LANGUAGE SQL |
4、需要设置新的结束标记
1 | DELIMITER 新的结束标记 |
因为MySQL默认的语句结束符号为分号‘;’。为了避免与存储过程中SQL语句结束符相冲突,需要使用 DELIMITER改变存储过程的结束符。
示例:
1 | DELIMITER $ |
2.2 代码举例
举例1:创建存储过程select_all_data(),查看 emps 表的所有数据
1 | DELIMITER $ |
举例2:创建存储过程avg_employee_salary(),返回所有员工的平均工资
1 | CREATE PROCEDURE avg_employee_salary() |
举例3:创建存储过程show_min_salary(),查看“emps”表的最低薪资值。并将最低薪资通过OUT参数“ms” 输出
1 | DELIMITER // |
举例4:创建存储过程show_someone_salary(),查看“emps”表的某个员工的薪资,并用IN参数empname 输入员工姓名。
1 | DELIMITER // |
举例5:创建存储过程show_someone_salary2(),查看“emps”表的某个员工的薪资,并用IN参数empname 输入员工姓名,用OUT参数empsalary输出员工薪资。
1 | DELIMITER // |
举例6:创建存储过程show_mgr_name(),查询某个员工领导的姓名,并用INOUT参数“empname”输入员 工姓名,输出领导的姓名。
1 | DELIMITER // |
3. 调用存储过程
3.1 调用格式
存储过程有多种调用方法。存储过程必须使用CALL语句调用,并且存储过程和数据库相关,如果要执行 其他数据库中的存储过程,需要指定数据库名称,例如CALL dbname.procname
。
1 | CALL 存储过程名(实参列表) |
1、调用in模式的参数:
1 | CALL sp1('值'); |
2、调用out模式的参数:
1 | SET @name; |
3、调用inout模式的参数:
1 | SET @name=值; |
如果你用的是 Navicat 工具,那么在编写存储过程的时候,Navicat 会自动设置 DELIMITER 为其他符号, 我们不需要再进行 DELIMITER 的操作。 直接使用 CALL add_num(50); 即可。这里我传入的参数为 50,也就是统计 1+2+…+50 的积累之和。
4. 存储函数的使用
4.1 语法分析
语法格式:
1 | CREATE FUNCTION 函数名(参数名 参数类型,...) |
说明:
1、参数列表:指定参数为IN、OUT或INOUT只对PROCEDURE是合法的,FUNCTION中总是默认为IN参 数。
2、RETURNS type 语句表示函数返回数据的类型; RETURNS子句只能对FUNCTION做指定,对函数而言这是 强制 的。它用来指定函数的返回类型,而且函 数体必须包含一个 RETURN value 语句。
3、characteristic 创建函数时指定的对函数的约束。取值与创建存储过程时相同,这里不再赘述。
4、函数体也可以用BEGIN…END来表示SQL代码的开始和结束。如果函数体只有一条语句,也可以省略 BEGIN…END。
4.2 调用存储函数
在MySQL中,存储函数的使用方法与MySQL内部函数的使用方法是一样的。换言之,用户自己定义的存 储函数与MySQL内部函数是一个性质的。区别在于,存储函数是 用户自己定义 的,而内部函数是MySQL 的 开发者定义的。
1 | SELECT 函数名(实参列表) |
4.3 代码举例:
举例一:创建存储函数,名称为email_by_name(),参数定义为空,该函数查询Abel的email,并返回,数据类型为 字符串型。
1 | CREATE FUNCTION email_by_name() |
1 | # 调用 |
举例二:创建存储函数,名称为email_by_id(),参数传入emp_id,该函数查询emp_id的email,并返回,数据类型 为字符串型。
1 | DELIMITER // |
1 | # 调用 |
注意:
若在创建存储函数中报错“ you might want to use the less safe log_bin_trust_function_creators variable ”,有两种处理方法:
方式1:加上必要的函数特性“[NOT] DETERMINISTIC”和“{CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}”
方式2:
1 mysql> SET GLOBAL log_bin_trust_function_creators = 1;
4.4 对比存储函数和存储过程
关键字 | 调用语法 | 返回值 | 应用场景 | |
---|---|---|---|---|
存储过程 | PROCEDURE | CALL 存储过程() | 理解为有0个或 多个 | 一般用于更新 |
存储函数 | FUNCTION | SELECT 函数() | 只能是一个 | 一般用于查询结果为一个值并返回时 |
此外,存储函数可以放在查询语句中使用,存储过程不行。反之,存储过程的功能更加强大,包括能够 执行对表的操作(比如创建表,删除表等)和事务操作,这些功能是存储函数不具备的。
5. 存储过程和函数的查看、修改、删除
5.1 查看
MySQL存储了存储过程和函数的状态信息,用户可以使用SHOW STATUS
语句或SHOW CREATE
语句来查 看,也可直接从系统的information_schema数据库中查询。这里介绍3种方法。
1. 使用SHOW CREATE语句查看存储过程和函数的创建信息
基本语法结构如下:
1 | SHOW CREATE {PROCEDURE | FUNCTION} 存储过程名或函数名 |
举例:
1 | SHOW CREATE FUNCTION test_db.CountProc \G |
2. 使用SHOW STATUS语句查看存储过程和函数的状态信息
基本语法结构如下:
1 | SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern'] |
这个语句返回子程序的特征,如数据库、名字、类型、创建者及创建和修改日期。
举例
1 | mysql> SHOW PROCEDURE STATUS LIKE 'SELECT%' \G |
3. 从information_schema.Routines表中查看存储过程和函数的信息
MySQL中存储过程和函数的信息存储在information_schema数据库下的Routines表中。可以通过查询该表 的记录来查询存储过程和函数的信息。其基本语法形式如下:
1 | SELECT * FROM information_schema.Routines |
说明:如果在MySQL数据库中存在存储过程和函数名称相同的情况,最好指定ROUTINE_TYPE查询条件来 指明查询的是存储过程还是函数。
举例:从Routines表中查询名称为CountProc的存储函数的信息,代码如下:
1 | SELECT * FROM information_schema.Routines |
5.2 修改
修改存储过程或函数,不影响存储过程或函数功能,只是修改相关特性。使用ALTER语句实现。
1 | ALTER {PROCEDURE | FUNCTION} 存储过程或函数的名 [characteristic ...] |
其中,characteristic指定存储过程或函数的特性,其取值信息与创建存储过程、函数时的取值信息略有 不同。
1 | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } |
CONTAINS SQL ,表示子程序包含SQL语句,但不包含读或写数据的语句。
NO SQL ,表示子程序中不包含SQL语句。
READS SQL DATA ,表示子程序中包含读数据的语句。
MODIFIES SQL DATA ,表示子程序中包含写数据的语句。
SQL SECURITY { DEFINER | INVOKER } ,指明谁有权限来执行。
- DEFINER ,表示只有定义者自己才能够执行。
- INVOKER ,表示调用者可以执行。
COMMENT ‘string’ ,表示注释信息
5.3 删除
删除存储过程和函数,可以使用DROP语句,其语法结构如下:
1 | DROP {PROCEDURE | FUNCTION} [IF EXISTS] 存储过程或函数的名 |
IF EXISTS:如果程序或函数不存储,它可以防止发生错误,产生一个用SHOW WARNINGS查看的警告。
举例:
1 | DROP PROCEDURE CountProc; |
6. 优点和缺点
优点
**1、存储过程可以一次编译多次使用。**存储过程只在创建时进行编译,之后的使用都不需要重新编译, 这就提升了 SQL 的执行效率。
**2、可以减少开发工作量。**将代码 封装 成模块,实际上是编程的核心思想之一,这样可以把复杂的问题 拆解成不同的模块,然后模块之间可以 重复使用 ,在减少开发工作量的同时,还能保证代码的结构清 晰。
**3、存储过程的安全性强。**我们在设定存储过程的时候可以 设置对用户的使用权限 ,这样就和视图一样具 有较强的安全性。
**4、可以减少网络传输量。**因为代码封装到存储过程中,每次使用只需要调用存储过程即可,这样就减 少了网络传输量。
**5、良好的封装性。**在进行相对复杂的数据库操作时,原本需要使用一条一条的 SQL 语句,可能要连接 多次数据库才能完成的操作,现在变成了一次存储过程,只需要 连接一次即可 。
缺点
**1、可移植性差。**存储过程不能跨数据库移植,比如在 MySQL、Oracle 和 SQL Server 里编写的存储过 程,在换成其他数据库时都需要重新编写。
**2、调试困难。只有少数 DBMS 支持存储过程的调试。**对于复杂的存储过程来说,开发和维护都不容 易。虽然也有一些第三方工具可以对存储过程进行调试,但要收费。
**3、存储过程的版本管理很困难。**比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发 软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。
**4、它不适合高并发的场景。**高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方 式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护, 增加数据库的压力 ,显然就 不适用了。
7. 课后练习
准备工作
1 | CREATE DATABASE test15_pro_func; |
- 创建存储过程insert_user(),实现传入用户名和密码,插入到admin表
1 | CREATE TABLE admin( |
- 创建存储过程get_phone(),实现传入女神编号,返回女神姓名和女神电话
1 | CREATE TABLE beauty( |
- 创建存储过程date_diff(),实现传入两个女神生日,返回日期间隔大小
1 | DELIMITER $ |
变量,流程控制和游标
在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终 的结果数据。
在 MySQL 数据库中,变量分为 系统变量 以及 用户自定义变量 。
1. 变量
1.1 系统变量
- 查看所有或部分系统变量
1 | #查看所有全局变量 |
1 | #查看满足条件的部分系统变量。 |
举例:
1 | SHOW GLOBAL VARIABLES LIKE 'admin_%'; |
- 查看指定系统变量
作为 MySQL 编码规范,MySQL 中的系统变量以 两个“@” 开头,其中“@@global”仅用于标记全局系统变 量,“@@session”仅用于标记会话系统变量。“@@”首先标记会话系统变量,如果会话系统变量不存在, 则标记全局系统变量。
1 | #查看指定的系统变量的值 |
- 修改系统变量的值
有些时候,数据库管理员需要修改系统变量的默认值,以便修改当前会话或者MySQL服务实例的属性、 特征。具体方法:
方式1:修改MySQL 配置文件 ,继而修改MySQL系统变量的值(该方法需要重启MySQL服务)
方式2:在MySQL服务运行期间,使用“set”命令重新设置系统变量的值
1 | #为某个系统变量赋值 |
举例:
1 | SELECT @@global.autocommit; |
1 | SELECT @@session.tx_isolation; |
1.2 用户变量
1.2.1 用户变量分类
用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以一个“@”
开头。根据作用 范围不同,又分为会话用户变量
和局部变量
。
会话用户变量:作用域和会话变量一样,只对
当前连接
会话有效。局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在
存储过程和函数
中使用。
1.2.2 会话用户变量
- 变量的定义
1 | #方式1:“=”或“:=” |
- 查看用户变量的值 (查看、比较、运算等)
1 | SELECT @用户变量 |
- 举例
1 | SELECT @num := COUNT(*) FROM employees; |
1.2.3 局部变量
定义:可以使用 DECLARE 语句定义一个局部变量
作用域:仅仅在定义它的 BEGIN … END 中有效
位置:只能放在 BEGIN … END 中,而且只能放在第一句
1 | BEGIN |
- 定义变量
1 | DECLARE 变量名 类型 [default 值]; # 如果没有DEFAULT子句,初始值为NULL |
举例:
1 | DECLARE myparam INT DEFAULT 100; |
- 变量赋值
1 | # 方式1:一般用于赋简单的值 |
- 使用变量(查看、比较、运算等)
1 | SELECT 局部变量名; |
举例1:声明局部变量,并分别赋值为employees表中employee_id为102的last_name和salary
1 | CREATE PROCEDURE set_value() |
1.2.4 对比会话用户变量与局部变量
作用域 | 定义位置 | 语法 | |
---|---|---|---|
会话用户变量 | 当前会话 | 会话的任何地方 | 加@符号,不用指定类型 |
局部变量 | 定义它的BEGIN END中 | BEGIN END的第一句话 | 一般不用加@,需要指定类型 |
2. 流程控制
解决复杂问题不可能通过一个 SQL 语句完成,我们需要执行多个 SQL 操作。流程控制语句的作用就是控 制存储过程中 SQL 语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程 就分为三大类:
- 顺序结构 :程序从上往下依次执行、
- 分支结构 :程序按条件进行选择执行,从两条或多条路径中选择一条执行
- 循环结构 :程序满足一定条件下,重复执行一组语句
针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序。
- 条件判断语句 :IF 语句和 CASE 语句
- 循环语句 :LOOP、WHILE 和 REPEAT 语句
- 跳转语句 :ITERATE 和 LEAVE 语句
2.1 分支结构之 IF
IF 语句的语法结构是:
1 | IF 表达式1 THEN 操作1 |
根据表达式的结果为TRUE或FALSE执行相应的语句。这里“[]”中的内容是可选的。
- 特点:① 不同的表达式对应不同的操作 ② 使用在begin end中
举例:声明存储过程“update_salary_by_eid2”,定义IN参数emp_id,输入员工编号。判断该员工 薪资如果低于9000元并且入职时间超过5年,就涨薪500元;否则就涨薪100元。
1 | DELIMITER // |
2.2 分支结构之 CASE
- CASE 语句的语法结构1:
1 | #情况一:类似于switch |
举例:使用CASE流程控制语句的第1种格式,判断val值等于1、等于2,或者两者都不等。
1 | CASE val |
- CASE 语句的语法结构2:
1 | #情况二:类似于多重if |
举例:使用CASE流程控制语句的第2种格式,判断val是否为空、小于0、大于0或者等于0。
1 | CASE |
2.3 循环结构之LOOP
LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE
子 句),跳出循环过程。
LOOP语句的基本格式如下:
1 | [loop_label:] LOOP |
其中,loop_label表示LOOP语句的标注名称,该参数可以省略。
举例
1 | # 举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程 |
2.4 循环结构之WHILE
WHILE语句创建一个带条件判断的循环过程。WHILE在执行语句执行时,先对指定的表达式进行判断,如 果为真,就执行循环内的语句,否则退出循环。WHILE语句的基本格式如下:
1 | [while_label:] WHILE 循环条件 DO |
while_label为WHILE语句的标注名称;如果循环条件结果为真,WHILE语句内的语句或语句群被执行,直 至循环条件为假,退出循环。
举例:WHILE语句示例,i值小于10时,将重复执行循环过程,代码如下:
1 | CREATE PROCEDURE test_while() |
2.5 循环结构之REPEAT
REPEAT语句创建一个带条件判断的循环过程。与WHILE循环不同的是,REPEAT 循环首先会执行一次循 环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会 就继续执行循环,直到满足退出条件为止。
REPEAT语句的基本格式如下:
1 | [repeat_label:] REPEAT |
repeat_label为REPEAT语句的标注名称,该参数可以省略;REPEAT语句内的语句或语句群被重复,直至expr_condition为真。
举例一:
1 | CREATE PROCEDURE test_repeat() |
对比三种循环
1、这三种循环都可以省略名称,但如果循环中添加了循环控制语句(LEAVE或ITERATE)则必须添加名称。
2、 LOOP:一般用于实现简单的”死”循环 WHILE:先判断后执行 REPEAT:先执行后判断,无条件 至少执行一次
2.6 跳转语句之LEAVE语句
LEAVE语句:可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出 程序体的操作。如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 break。
基本格式如下:
1 | LEAVE 标记名 |
其中,label参数表示循环的标志。LEAVE和BEGIN … END或循环一起被使用。
创建存储过程 “leave_begin()”,声明INT类型的IN参数num。给BEGIN…END加标记名,并在 BEGIN…END中使用IF语句判断num参数的值。
如果num<=0,则使用LEAVE语句退出BEGIN…END;
如果num=1,则查询“employees”表的平均薪资;
如果num=2,则查询“employees”表的最低薪资;
如果num>2,则查询“employees”表的最高薪资。
1 | CREATE PROCEDURE leave_begin(IN num INT) |
2.7 跳转语句之ITERATE语句
ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序 转到语句段开头处。如果你有面向过程的编程语言的使用经验,你可以把 ITERATE 理解为 continue,意 思为“再次循环”。
语句基本格式如下:
1 | ITERATE label |
label参数表示循环的标志。ITERATE语句必须跟在循环标志前面。
举例: 定义局部变量num,初始值为0。循环结构中执行num + 1操作。
- 如果num < 10,则继续执行循环;
- 如果num > 15,则退出循环结构;
1 | CREATE PROCEDURE test_iterate() |
3. 游标
3.1 什么是游标?
虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录, 但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一 条记录
,并对记录的数据进行处理。
这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录 进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过 程开发的能力。
在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了 指针的作用
,我们可以通过操作游标来对数据行进行操作。
3.2 使用游标步骤
游标必须在声明处理程序之前被声明,并且变量和条件还必须在声明游标或处理程序之前被声明。
如果我们想要使用游标,一般需要经历四个步骤。不同的 DBMS 中,使用游标的语法可能略有不同。
第一步,声明游标
在MySQL中,使用DECLARE关键字来声明游标,其语法的基本形式如下:
1 | DECLARE cursor_name CURSOR FOR select_statement; |
这个语法适用于 MySQL,SQL Server,DB2 和 MariaDB。如果是用 Oracle 或者 PostgreSQL,需要写成:
1 | DECLARE cursor_name CURSOR IS select_statement; |
要使用 SELECT 语句来获取数据结果集,而此时还没有开始遍历数据,这里select_statement
代表的是 SELECT 语句,返回一个用于创建游标的结果集。
例如:
1 | DECLARE cur_emp CURSOR FOR |
第二步,打开游标
1 | OPEN cursor_name |
当我们定义好游标之后,如果想要使用游标,必须先打开游标。打开游标的时候 SELECT 语句的查询结 果集就会送到游标工作区,为后面游标的逐条读取
结果集中的记录做准备。
第三步,使用游标(从游标中取得数据)
1 | FETCH cursor_name INTO var_name [, var_name] ... |
这句的作用是使用 cursor_name 这个游标来读取当前行,并且将数据保存到 var_name 这个变量中,游 标指针指到下一行。如果游标读取的数据行有多个列名,则在 INTO 关键字后面赋值给多个变量名即可。
注意:var_name必须在声明游标之前就定义好。
注意:游标的查询结果集中的字段数,必须跟 INTO 后面的变量数一致,否则,在存储过程执行的时候,MySQL 会提示错误。
第四步,关闭游标
1 | CLOSE cursor_name |
有 OPEN 就会有 CLOSE,也就是打开和关闭游标。当我们使用完游标后需要关闭掉该游标。因为游标会占用系统资源
,如果不及时关闭,游标会一直保持到存储过程结束,影响系统运行的效率。而关闭游标 的操作,会释放游标占用的系统资源。
关闭游标之后,我们就不能再检索查询结果中的数据行,如果需要检索只能再次打开游标。
- 举例
1 | # 创建存储过程“get_count_by_limit_total_salary()”,声明IN参数 limit_total_salary,DOUBLE类型;声明 |
3.5 小结
游标是 MySQL 的一个重要的功能,为逐条读取
结果集中的数据,提供了完美的解决方案。跟在应用层 面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。
但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁
,这样在业务并发量大 的时候,不仅会影响业务之间的效率,还会 消耗系统资源
,造成内存不足,这是因为游标是在内存中进 行的处理。
建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。
触发器
1. 触发器的概述
触发器是由事件来触发
某个操作,这些事件包括NSERT
、UPDATE
、DELETE
事件。所谓事件就是指 用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生 了,就会自动
激发触发器执行相应的操作。
当对数据表中的数据执行插入、更新和删除操作,需要自动执行一些数据库逻辑时,可以使用触发器来 实现。
2. 触发器的创建
2.1 创建触发器语法
创建触发器的语法结构是:
1 | CREATE TRIGGER 触发器名称 |
说明
表名 :表示触发器监控的对象
BEFORE|AFTER :表示触发的时间。BEFORE 表示在事件之前触发;AFTER 表示在事件之后触发。
INSERT|UPDATE|DELETE :表示触发的事件。
- INSERT 表示插入记录时触发;
- UPDATE 表示更新记录时触发;
- DELETE 表示删除记录时触发。
触发器执行的语句块 :可以是单条SQL语句,也可以是由BEGIN…END结构组成的复合语句块。
2.2 代码举例
1、创建表
1 | CREATE TABLE test_trigger ( |
2、创建触发器:创建名称为before_insert的触发器,向test_trigger数据表插入数据之前,向 test_trigger_log数据表中插入before_insert的日志信息。
1 | DELIMITER // |
3、向test_trigger表中插入记录
1 | INSERT INTO test_trigger (t_note) VALUES ('测试 BEFORE INSERT 触发器'); |
4、查询数据
1 | SELECT * FROM test_trigger_log; |
3. 查看、删除触发器
3.1 查看触发器
查看触发器是查看数据库中已经存在的触发器的定义、状态和语法信息等。
1 | # 方式1:查看当前数据库的所有触发器的定义 |
3.2 删除触发器
1 | DROP TRIGGER IF EXISTS 触发器名称; |
4. 触发器的优缺点
优点
1、触发器可以确保数据的完整性。
2、触发器可以帮助我们记录操作日志。
3、触发器还可以用在操作数据前,对数据进行合法性检查。
缺点
1、触发器最大的一个问题就是可读性差。
2、相关数据的变更,可能会导致触发器出错。