视图
视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视 图的查询中使用的表,并且是在使用视图时动态生成的。
通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。
语法
创建
- 创建
1 | CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH [CASCADED|LOCAL] CHECK OPTION ] |
现在我们基于表student建立一个视图(这里有个参数是or replace,表示是否覆盖原来的视图,创建的时候可以不加)
1 | -- 创建视图 |
查询
1 | -- 查看创建视图语句 |
修改
1 | -- 方式一 |
这里修改视图的第一种方式就必须携带or replace了。
删除
1 | DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ... |
检查选项
我们来试试向视图中插入数据:
1 | insert into stu_v_1 values(6,'Tom'); |
在基表当中,我们可以发现两条数据都插入了:
但是视图当中只有id为6的:
因为我们在创建视图的时候,是相当于把数据先插入到基表中,然后再重新创建一个视图,这时会重新判断条件,指定的条件为 id<=10, id为17的数据,是不符合条件的,所以没有查询出来。
如果我们定义视图时,如果指定了条件,然后我们在插入、修改、删除数据时,是否可以做到必须满足条件才能操作,否则不能够操作呢?
答案是可以的,这就需要借助于视图的检查选项了。
当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插 入,更新,删除,以使其符合视图的定义。
MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。
为了确定检查的范围,mysql提供了两个选项: CASCADED 和 LOCAL ,默认值为 CASCADED 。
我们带上检查选项再次创建视图:
1 | create or replace view stu_v_1 as select id,name from student where id<=10 with check option; |
这时我们再次插入刚才的数据,发现6还是不影响,但是17就插入失败了:
这是因为加上检查选项之后,系统就会在你每次更新视图时,去检查行的条件是否符合创建视图时的条件。
我们知道检查选项有两个参数,现在我们就来分别对其进行剖析。
CASCADED
级联。
比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 cascaded,但是v1视图 创建时未指定检查选项。 则在执行检查时,不仅会检查v2,还会级联检查v2的关联视图v1,如下图:
我们首先创建一个不带检查选项的视图
1 | create or replace view stu_v_1 as select id,name from student where id <=20 |
它的插入都可以成功,因为它没有带检查选项。
我们再基于stu_v_1创建一个带cascaded的视图:
1 | create or replace view stu_v_2 as select id,name from stu_v_1 where id >=10 with cascaded check option ; |
这时我们再来插入几组数据:
- id为6
1 | insert into stu_v_2 values (6,'Tom'); |
插入失败,由于不满足它本身的条件
- id为13
1 | insert into stu_v_2 values (13,'Tom'); |
插入成功
- id为21
1 | insert into stu_v_2 values (21,'Tom'); |
插入失败,为什么呢?
明明21满足它本身的条件(id>=10),这是因为它是基于
stu_v_1的视图,同时又携带cascaded的检查选择,它会将这个设置传递给stu_v_1,让stu_v_1也会检查,整体呈现一种递归性。
我们再基于stu_v_2创建一个不带检查选项的视图:
1 | create or replace view stu_v_3 as select id,name from stu_v_2 where id <=15; |
我们插入几条数据:
- id为11和17
1 | insert into stu_v_2 values (11,'Tom'); |
他们都可以插入成功,因为
stu_v_3本身没有条件,所以不管满不满足它自己,都不会触发检查
- id为7和28
1 | insert into stu_v_2 values (7,'Tom'); |
这两条数据插入失败了,是为什么呢?
其实,就算这一层本身不带检查选项了,它也会逐层向上检查,看它所继承的视图是否有检查选项;它继承的
stu_v_2带检查选项,它进行逐层检查时发现条件不满足,于是就插入失败了。
local
本地。
比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 local ,但是v1视图创建时未指定检查选项。 则在执行检查时,知会检查v2,不会检查v2的关联视图v1,如下图:
我们创建三个视图
1 | create or replace view stu_v_4 as select id,name from student where id <=20; |
- 对v5插入数据
1 | insert into stu_v_5 values (6,'Tom'); |
只有6插入失败,因local不具备传递功能,你有检查选项就是有,没有就是没有,各自判断出来
- 对v6插入数据
1 | insert into stu_v_6 values (7,'Tom'); |
也只有7插入失败,原因同上
更新
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一 项,则该视图不可更新:
- 聚合函数或窗口函数(SUM()、 MIN()、 MAX()、 COUNT()等)
- DISTINCT
- GROUP BY
- HAVING
- UNION 或者 UNION ALL
我们来测试一下,创建一个带有count的视图:
1 | create view stu_v_count as select count(*) from student; |
上述的视图中,就只有一个单行单列的数据,如果我们对这个视图进行更新或插入的,将会报错。
1 | insert into stu_v_count values(10); |
作用
- 简单:视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。
- 安全:数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据。
- **数据独立:**视图可帮助用户屏蔽真实表结构变化带来的影响。
案例
- 为了保证数据库表的安全性,开发人员在操作tb_user表时,只能看到的用户的基本字段,屏蔽 手机号和邮箱两个字段。
1 | create view tb_user_view as select id,name,profession,age,gender,status,createtime from tb_user; |
如下图,只有我们想让对方看见的字段,避免了敏感字段,比如密码什么的、
- 查询每个学生所选修的课程(三张表联查),这个功能在很多的业务中都有使用到,为了简化操作,定义一个视图。
1 | create view tb_stu_course_view as select s.name student_name , s.no student_no ,c.name course_name from student s, student_course sc , course c where s.id = sc.studentid and sc.courseid = c.id; |
如下图,这样每次都只需要针对于这个视图进行操作即可,不用每次都写很长的连表条件。
存储过程
存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用:
特点
- 封装,复用:可以把某一业务SQL封装在存储过程中,需要用到的时候直接调用即可。
- **可以接收参数,也可以返回数据:**在存储过程中,可以传递参数,也可以接收返回值。
- 减少网络交互,效率提升: 如果涉及到多条SQL,每执行一次都是一次网络传 输。 而如果封装在存储过程中,我们只需要网络交互一次可能就可以了。
基本语法
创建
1 | CREATE PROCEDURE 存储过程名称 ([ 参数列表 ]) |
注意:在命令行中,执行创建存储过程的SQL时,需要通过关键字
delimiter指定SQL语句的结束符(指定为除;的其他符号)。因为命令行自动识别
;为结束符,它识别到begin和end中间的分号时,就认为这一条SQL语句已经写完了,就会出现错误。
调用
1 | CALL 名称 ([ 参数 ]); |
查看
1 | -- 查询指定数据库的存储过程及状态信息 |
删除
1 | DROP PROCEDURE [IF EXISTS] 存储过程名称; |
变量
在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量。
系统变量
系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话 变量(SESSION)。
- 查看系统变量
1 | -- 查看所有系统变量 |
查询所有系统变量:
查看以auto开头的系统变量:
知道名字直接查当前会话中的系统变量:
1 | select @@session.autocommit; |
- 设置系统变量
1 | SET [SESSION|GLOBAL] 系统变量名 = 值 ; |
注意: 如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
- 全局变量(GLOBAL): 全局变量针对于所有的会话。
- 会话变量(SESSION): 会话变量针对于单个会话,在另外一个会话窗口就不生效了。
mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。
用户定义变量
用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 “@变量名” 使用就可以。其作用域为当前连接,只能在当前会话中使用。
- 赋值
1 | SET @var_name = expr [, @var_name = expr] ... ; |
赋值时,可以使用 = ,也可以使用 := 。
1 | SELECT @var_name := expr [, @var_name := expr] ... ; |
方式一:
1 | set @myname = 'itcast'; |
- 推荐使用
:=,因为sql里面比较运算符只有=,用:=比较好区分。
方式二
1 | select @myphone :='xiaomi'; |
- 使用
1 | SELECT @var_name; |
如下展示刚刚生成的变量:
1 | select @myname,@myage,@mygender,@myhobby; |
1 | select @myphone,@mycount; |
注意: 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
局部变量
局部变量是根据需要定义的在局部生效的变量,访问之前,需要
DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN … END块。
- 声明
1 | DECLARE 变量名 变量类型 [DEFAULT ... ] ; |
变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。
- 赋值
1 | SET 变量名 = 值 ; |
示例
default后跟初始化值,如果不用初始值可以省略。
1 | create procedure p2() |
if
在if条件判断的结构中,ELSE IF 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有。
1 | IF 条件1 THEN |
案例
根据定义的分数score变量,判定当前分数对应的分数等级。
- score >= 85分,等级为优秀。
- score >= 60分 且 score < 85分,等级为及格。
- score < 60分,等级为不及格。
1 | create procedure p3() |
上述的需求我们虽然已经实现了,但是也存在一些问题,比如:score 分数我们是在存储过程中定义死的,而且最终计算出来的分数等级,我们也仅仅是最终查询展示出来而已。
那么我们能不能,把score分数动态的传递进来,计算出来的分数等级是否可以作为返回值返回呢?答案是肯定的,我们可以通过接下来所讲解的参数来解决上述的问题。
参数
参数的类型,主要分为以下三种:IN、OUT、INOUT。 具体的含义如下:
| 类型 | 含义 |
|---|---|
| in | 该类参数作为输入,也就是需要调用时传入值 |
| out | 该类参数作为输出,也就是该参数可以作为返回值 |
| inout | 既可以作为输入参数,也可以作为输出参数 |
语法:
1 | CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ]) |
根据传入参数score,判定当前分数对应的分数等级,并返回。
- score >= 85分,等级为优秀。
- score >= 60分 且 score < 85分,等级为及格。
- score < 60分,等级为不及格。
1 | create procedure p4(in score int, out result varchar(10)) |
case
case结构及作用,和我们在基础篇中所讲解的流程控制函数很类似。
有两种语法格式:
语法1:
含义: 当case_value的值为 when_value1时,执行statement_list1,当值为 when_value2时执行statement_list2, 否则就执行 statement_list
1 | CASE case_value |
语法2:
含义: 当条件search_condition1成立时,执行statement_list1,当条件search_condition2成 立时,执行statement_list2, 否则就执行 statement_list
1 | CASE |
案例
根据传入的月份,判定月份所属的季节(要求采用case结构)。
- 1-3月份,为第一季度
- 4-6月份,为第二季度
- 7-9月份,为第三季度
- 10-12月份,为第四季度
1 | create procedure p5(in month int) |
while
while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。具体语法为:
先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
1 | WHILE 条件 DO |
案例
计算从1累加到n的值,n为传入的参数值。
1 | create procedure p6(in n int) |
repeat
repeat是有条件的循环控制语句, 当满足until声明的条件的时候,则退出循环 。具体语法为:
先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环,直到满足某个条件退出循环
1 | REPEAT |
案例
计算从1累加到n的值,n为传入的参数值。(使用repeat实现)
1 | create procedure p7(in n int) |
loop
LOOP 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。 LOOP可以配合一下两个语句使用:
- LEAVE :配合循环使用,退出循环。
- ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
下面语法中出现的 begin_label,end_label,label 指的都是我们所自定义的标记。
1 | [begin_label:] LOOP |
1 | LEAVE label; -- 退出指定标记的循环体 |
案例1
计算从1累加到n的值,n为传入的参数值。
1 | create procedure p8(in n int) |
案例2
计算从1到n之间的偶数累加的值,n为传入的参数值。
1 | create procedure p9(in n int) |
cursor
游标(CURSOR)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下。
- 声明游标
1 | DECLARE 游标名称 CURSOR FOR 查询语句 ; |
- 打开游标
1 | OPEN 游标名称 ; |
- 获取游标记录
1 | FETCH 游标名称 INTO 变量 [, 变量 ] ; |
- 关闭游标
1 | CLOSE 游标名称 ; |
案例
根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名 (name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表 (id,name,profession)中。
1 | create procedure p11(in uage int) |
但是当我们执行过后会报错
找不到数据。
我们在循环中用游标不断填写数据的时候,到最后一次它没有数据了,就会跳出循环,但是这时就会报错,那我们怎么解决呢?
要想解决这个问题,就需要通过MySQL中提供的 条件处理程序 Handler 来解决
条件处理程序
条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为:
1 | -- for后面跟上的就是条件,满足什么样的条件我才执行handle这个动作 |
我们继续来完成在上一小节提出的这个需求,并解决其中的问题。
- 通过SQLSTATE指定具体的状态码(02000)
1 | declare exit handler for SQLSTATE '02000' close u_cursor; |
这里的SQLSTATE的值其实就是上面报错的SQL状态码,表示只要出现这种情况,我们就会退出当前程序。
1 | create procedure p11(in uage int) |
这次不报错了:
表中也有对应数据:
具体的错误状态码,可以参考官方文档:
https://dev.mysql.com/doc/refman/8.0/en/declare-handler.html
https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html
存储函数
存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。
具体语法如下:
1 | CREATE FUNCTION 存储函数名称 ([ 参数列表 ]) |
characteristic说明:
- DETERMINISTIC:相同的输入参数总是产生相同的结果。
- NO SQL :不包含SQL语句。
- READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句。
案例
计算从1累加到n的值,n为传入的参数值。
1 | create function fun1(n int) |
PS:其实用的很少,因为存储函数能解决的,存储过程肯定也可以。
触发器
触发器是与表有关的数据库对象,指在insert/update/delete之前(BEFORE)或之后(AFTER),触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作 。
使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。
| 触发器类型 | new和old |
|---|---|
| INSERT 型触发器 | NEW表示将要或者已经新增的数据 |
| UPDATE 型触发器 | OLD表示修改之前的数据 ,NEW表示将要或已经修改后的数据 |
| DELETE 型触发器 | OLD表示将要或者已经删除的数据 |
语法
创建
1 | CREATE TRIGGER trigger_name |
查看
1 | SHOW TRIGGERS; |
删除
1 | -- 如果没有指定 schema_name,默认为当前数据库 。 |
案例
通过触发器记录 tb_user 表的数据变更日志,将变更日志插入到日志表user_logs中, 包含增加, 修改 , 删除 ;
表结构准备:
1 | create table user_logs( |
插入数据触发器
1 | create trigger tb_user_insert_trigger |
插入数据:
1 | insert into tb_user(id, name, phone, email, profession, age, gender, status, createtime) VALUES (26,'三皇子','18809091212','erhuangzi@163.com','软件工程',23,'1','1',now()); |
更新数据触发器
1 | create trigger tb_user_update_trigger |
更新数据:
1 | update tb_user set profession = '会计' where id <= 5; |
可以看见插入了五条日志,这是因为MySQL只支持行级触发器,这里更新五条数据就是五条日志了。
删除数据触发器
1 | create trigger tb_user_delete_trigger |
删除数据:
1 | delete from tb_user where id = 26; |

