四. 视图/存储过程/触发器
4.1 视图
是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且实在使用视图时动态生成的。
4.1.1 基本语法
- 创建
create [or replace] view 视图名称[(视图列表)] as select语句 [with [cascaded | local] check option]
- 查询
查看创建视图语句:show create view 视图名称;
查看视图数据:select * from 视图名称....;
- 修改
方式一:create [or replace] view 视图名称[(列明列表)] as select语句 [with [cascaded | local] check option]
方式二:alter view 视图名称[(列表列名)] as select语句 [with [cascaded | local] check option]
- 删除
drop view [if exists] 视图名称 [视图名称]...
- 示例
#创建视图
create or replace view stu_v_1 as select id,name from student where id<=10;
#查询视图
show create view stu_v_1;
select * from stu_v_1;
select * from stu_v_1 where id<3;
#修改视图
create or replace view stu_v_1 as select id,name,no from student where id<=10;
alter view stu_v_1 as select id,name from student where id<=10;
#删除视图
drop view if exists stu_v_1;
4.1.2 检查选项
视图的检查选项
当使用with check option自居创建视图时,MySQL会通过视图检查正在更改的每个行,例如插入、删除、更新、以使其符合视图的定义。MySQL语序基于另一个视图创建视图,他还会依赖视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项:cascaded和local,默认值为cascaded。
cascaded:如果视图创建时定义了cascaded检查选项,那么在对视图进行更新操作时,不仅要去检查是否满足视图创建时的条件,还要递归地去检查是否满足他所依赖的视图的条件。
cascaded
创建视图v1时后面没有加with check option,此时对v1进行增删改操作他是不会去检查这个条件的。
create view v1 as select id,name from student where id<=20;
创建基于v1的视图v2,并且给他指定with cascaded check option,此时当我们操作v2这个视图时,由于cascaded
表示级联,他不仅要去检查当前操作是否满足v2的条件,还要去检查是否满足v2所依赖的v1的条件,就相当于在v1视图里
也加上了with cascaded check option,如果v1也关联了另外的视图同样如此。
create view v2 as select id,name from student where id>=10 with cascaded check option;
create or replace view stu_v_1 as select id,name from student where id<=20;
#插入成功,满足stu_v_1中的条件
insert into stu_v_1 values(5,'Tom');
#数据成功插入到基表中,然而视图中对应的数据并不存在,因为他不满足stu_v_1的条件。
insert into stu_v_1 values(25,'Tom');
create or replace view stu_v_2 as select id,name from
stu_v_1 where id>=10 with cascaded check option;
#由于上面的视图创建语句加了with cascaded check option,首先要去检查是否满足stu_v_2中的条件,不满足,执行失败。
insert into stu_v_2 values(7,'Tom');
#首先检查是否满足stu_v_2中的条件,如果满足,再去检查stu_v_2所依赖的底层的所有视图,由于插入的数据不满足stu_v_1的条件,所以执行失败。
insert into stu_v_2 values(26,'Tom');
#插入成功,满足stu_v_2和stu_v_1中的条件。
insert into stu_v_2 values(26,'Tom');
create or replace view stu_v_3 as select id,name from
stu_v_2 where id<=15;
#执行成功,满足stu_v_3,stu_v_2和stu_v_1中的条件。
insert into stu_v_3 values(11,'Tom');
#执行成功,因为stu_v_3中没有加with cascaded check option,不会去检查stu_v_3中的条件,而stu_v_3依赖
#stu_v_2,所以他去检查stu_v_2的条件,并且由于stu_v_2中加了with cascaded check option,于是他又向下去
#检查是否满足stu_v_1中的条件,发现条件都满足。
insert into stu_v_3 values(17,'Tom');
#执行失败,stu_v_3中没有with cascaded check option,所以不去检查stu_v_3的条件,而stu_v_3依赖stu_v_2
#视图,stu_v_2视图中有with cascaded check option,所以他去检查是否满足stu_v_2的条件,满足条件,然后再
#去检查stu_v_2所依赖的stu_v_1的条件,发现不满足,插入失败。
insert into stu_v_3 values(28,'Tom');
local:如果视图创建时定义了local检查选项,那么在对视图进行更新操作时,需要去检查是否满足创建时的条件。如果条件满足,再去看他所依赖的视图在创建时是否也定义了检查选项,如果定义了检查选项,他还要去检查他所依赖的视图中的条件,如果没有定义,则不做检查。
local
#往v1视图插入数据时,不会去检查v1中的条件
create view v1 as select id,name from student where id<=15;
#往v2视图插入数据时,由于v2在创建时定义了with local check option,所以他会去检查v2中的条件,然后再看v2
#所依赖的v1,v1视图在创建时没有定义检查选项,因此不检查v1中的条件。
create view v2 as select id,name from v1 where id>=10 with local check option;
create view v3 as select id,name from v2 where id<20;
create or replace view stu_v_4 as select id,name from student where id<=17;
#满足条件,直接插入到基表中
insert into stu_v_4 values(5,'Tom');
#不满足条件,但由于stu_v_4 在创建时没有定义检查选项,因此不对条件做检查,直接插入到基表中。
insert into stu_v_4 values(16,'Tom');
create or replace view stu_v_5 as select id,name from
stu_v_4 where id>=10 with local check option;
#执行成功。由于stu_v_5在创建时定义了检查选项with local check option,因此他需要去检查是否满足stu_v_5的
#条件,如果满足,再去看他所依赖的视图stu_v_4,由于stu_v_4没有定义检查选项,所以不检查stu_v_4中的条件。
insert into stu_v_5 values(13,'Tom');
#执行成功。只需要检查stu_v_5中的条件,不需要检查stu_v_4中的条件
insert into stu_v_5 values(17,'Tom');
create or replace view stu_v_6 as select id,name from
stu_v_5 where id<20;
#执行成功。首先由于stu_v_6中没有定义检查选项昂,所以不做检查。而他所依赖的stu_v_5中定义了检查选项,所以要
#检查,发现条件满足。然后在去看stu_v_5所依赖的stu_v_4,stu_v_4在创建时没有定义检查选项,因此也不做检查。
insert into stu_v_6 values(14,'Tom');
4.1.3 视图的更新
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则视图不可更新:
- 聚合函数或窗口函数( sum()、min()、max()、count()等)
- distinct
- group by
- having
- union 或者 union all
示例:
使用聚合函数创建视图,并往视图中插入数据,可以看到数据插入失败。因为我们创建视图时使用了聚合函数,可以看到视图中的数据和表中的数据不是一一对应的,所以此时视图不能进行插入和更新。
视图的作用:
- 简单:视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些经常被使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。
- 安全:数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据
- 数据独立:视图可以帮助用户屏蔽真实表结构变化带来的影响。
4.2 存储过程
4.2.1 介绍
是实现经过变异并存储在数据库中的一段SQL语句的集合,盗用存储过程可以简化应用开发人员的很多工作,减少数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。主要思想:数据库SQL语言层面的代码封装与重用。
特点:
(1)封装,复用
(2)可以接受参数,也可以返回数据
(3)减少网络交互,效率提升
4.2.2 基本语法
- 创建
create procedure 存储过程名称([参数列表])
begin
--SQL语句
endl
- 调用
call 名称([参数]);
- 查看
select * from information_schema.ROUTINES where ROUTINE_CHEMA='xxx'; --查询指定数据库的存储过程及状态信息
show create procedure 存储过程名称; --查询某个存储过程的定义
- 删除
drop procedure [if exists] 存储过程名称;
- 示例
#创建
create procedure p1()
begin
select count(*) from student;
end;
#调用
call p1();
#查看
select * from information_schema.ROUTINES where ROUTINE_SCHEMA='itcast';
show create procedure p1;
#删除
drop procedure p1;
注:在命令行中,执行创建存储过程的SQL时,需要通过关键字delimiter指定SQL语句的结束符。例如:delimiter $$
4.2.3 变量
- 系统变量是MySQL服务器提供,不是用户定义的,属于服务器层面,分为全局变量(global)、会话变量(session)。
如果是全局变量,是针对所有会话有效的。如果是会话变量,仅在当前会话有效。
(1)查看系统变量
show [session | global] variables; --查看所有系统变量
show [session | global] variables like '...'; --可以通过like模糊匹配方式查找变量
select @@[session | global] 系统变量名; --查看指定变量的值
(2)设置系统变量
set [session | global] 系统变量名=值;
set @@[session | global] 系统变量名=值;
注:
如果没有指定session/global,默认是session,会话变量。
mysql服务器重新启动之后,所设置的全局参数会失效,要想不是小,可以在/etc/my.cnf中配置。
- 用户定义变量是用户根据需要自己定义的变量,用户变量不需要提前声明在用的时候直接用“@变量名"使用就可以。其作用域为当前连接。(两个@@指的是系统变量,一个@指的是用户字定义变量)
(1)赋值
set @var_name = expr [,@var_name = expr]...;
set @var_name := expr [,@var_name := expr]...;
select @var_name := expr [,@var_name := expr]...;
select 字段名 into @var_name from 表名;
(2)使用
select @var_name;
(3)示例
#赋值
set @myname = 'itcast';
set @myage := 10;
set @mygender := '男',@myhobby := 'java';
select @mycolor:= 'red';
select count(*) into @mycount from tb_user;
#使用
select @myname,@myage,@mygender,@myhobby;
select @mycolor,@mycount;
注:用户自定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
- 局部变量是根据需要定义的在局部生效的变量,访问之前,需要declare声明。可用作存储过程内的局部比那辆和输入参数,局部变量的范围是在其内部声明的begin…end块。
(1)声明
declare 变量名 变量类型 [default...];
变量类型就是数据库字段类型:int, bigint, char, varchar, date, time等。
(2)赋值
set 变量名=值;
set 变量名 := 值;
select 字段名 into 变量名 from 表名...;
(3)示例
create procedure p2()
begin
declare stu_count int default 0;
select count(*) into stu_count from student;
select stu_count;
end;
#局部变量的范围是在begin和end之间,超出这个范围就没用了。
call p2();
4.2.4 if判断
基本语法:
if 条件1 then
...
else 条件2 then --可选
...
else --可选
...
end if;
示例:
--if
--根据定义的分数score变量,判定当前分数对应的分数等级
--score>=85分,等级为优秀
--score>=60且score<85分,等级为及格
--score<60分,等级为不及格。
create procedure p3()
begin
declare score int default 75;
declare result varchar(10);
if score >= 85 then
set result := '优秀';
else score >= 60 then
set result := '及格';
else
set result := '不及格';
else if;
select result; #查询结果为及格
end;
4.2.5 参数
- 参数
类型 | 含义 | 备注 |
---|---|---|
in | 该类参数作为输入,也就是需要调用传入值 | 默认 |
out | 该类参数作为输出,也就是该参数可以作为返回值 | |
inout | 既可以作为输入参数,也可以作为输出参数 |
- 用法
create procedure 存储过程名称([in/out/inout 参数名 参数类型])
begin
--SQL语句
end;
- 示例
--根据传入(in)的参数score,判定当前分数对应的分数等级,并返回(out)
--score>=85分,等级为优秀
--score>=60且score<85分,等级为及格
--score<60分,等级为不及格。
create procedure p4(in score int,out result varchar(10))
begin
declare result varchar(10);
if score >= 85 then
set result := '优秀';
else score >= 60 then
set result := '及格';
else
set result := '不及格';
else if;
end;
call p4(68,@result);
select @result; #查询结果为及格
--将传入的200分制的分数,进行换算,换算成百分制,然后返回
create procedure p5(inout score double)
begin
set score := score * 0.5;
end;
set @score = 78;
call p5(@score);
select @score; #最后查询结果为39
4.2.6 case
- 语法一
case case_value
when when_value1 then statement_list1
[when when_value2 then statement_list2]...
[else statement_list]
end case;
- 语法二
case
when search_condition1 then statement_list1
[when search_condition1 then statement_list1]...
[else statement_list]
end case;
- 示例
--case
--根据传入的月份,判定月份所属的季节(要求采用case结构)。
--1-3月份,为第一季度
--4-6月份,为第二季度
--7-9月份,为第三季度
--10-12月份,为第三季度
create procedure p6(in month int)
begin
declare result varchar(10);
case
when month >=1 and month <= 3 then
set result := '第一季度';
when month >=4 and month <= 6 then
set result := '第二季度';
when month >=7 and month <= 9 then
set result := '第三季度';
when month >=10 and month <= 12 then
set result := '第四季度';
else
set result := '非法参数';
select concat('您输入的月份为:',month,',所属的季度为:',result);
end;
call p6(4);
4.2.7 循环
- while
while循环是有条件的循环控制语句,满足条件后,再执行循环提中的SQL语句。
具体语法为:
#先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
while 条件 do
SQL逻辑...
end while;
示例:
--while 计算从1累加到n的值,n为传入的参数值
--A. 定义局部变量,记录累加后的值
--B. 每循环一次,就会对n进行减1,如果n减到0,则退出循环
create procedure p7(in n int)
begin
declare total int deault 0;
while n>0 do
set total := total + n;
set n := n - 1;
end while;
select total; #查询结果为55
end;
call p7(10);
- repeat
repeat是有条件的循环控制语句,退出循环。具体语法:
#先执行一次循环,然后判定逻辑是否满足,如果满足,则退出。如果不满足,则继续下一次循环
repeat
SQL逻辑...
until 条件
end repeat;
示例:
--repeat 计算计算从1累加到n的值,n为传入的参数值
--A. 定义局部变量,记录累加后的值
--B. 每循环一次,就会对n进行减1,如果n减到0,则退出循环
create procedure p8(in n int)
begin
declare total int deault 0;
repeat
set total := total + n;
n := n - 1;
until n <= 0
end repeat;
select total;
end;
call p8(10);
call p8(5050);
- loop
loop实现简单的循环,如果不在SQL逻辑中增加循环的条件,可以用其来实现简单的死循环。loop可以配合一下两个语句使用:
(1)leave:配合循环使用,退出循环。
(2)iterate:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
[begin_label:] loop
SQL逻辑...
end loop [end_label];
leave lable; --退出指定标记的循环体
iterate lable; --直接进入下一次循环
示例:
--loop 计算计算从1累加到n的值,n为传入的参数值
--A. 定义局部变量,记录累加后的值
--B. 每循环一次,就会对n进行减1,如果n减到0,则退出循环
create procedure p9(in n int)
begin
declare total int deault 0;
sum:loop
if n <= 0 then
leave sum;
end if;
set total := total + n;
n := n - 1;
end loop sum;
select total;
end;
call p9(100);
--loop 计算计算从1累加到n的值,n为传入的参数值
--A. 定义局部变量,记录累加后的值
--B. 每循环一次,就会对n进行减1,如果n减到0,则退出循环: leave xx;
--C. 如果当前累加的数据是奇数,则直接进入下一次循环: iterate xx;
create procedure p10(in n int)
begin
declare total int deault 0;
sum:loop
if n <= 0 then
leave sum; #n小于等于0,退出循环
end if;
if n % 2 = 1 then
set n := n - 1;
iterate sum; #n为奇数,进入下一次循环
end if;
set total := total + n;
n := n - 1;
end loop sum;
select total; #1-100直接之间的偶数累加结果为2550
end;
call p10(100);
4.2.8 游标
==游标(cursor)==是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标来对结果集进行循环的处理。游标的使用包含游标的声明、open、fetch、和close,其语法分别如下:
(1)声明游标
declare 游标名称 cursor for 查询语句;
(2)打开游标
open 游标名称
(3)获取游标记录
fetch 游标名称 into 变量[,变量];
(4)关闭游标
close 游标名称;
(5)示例
根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名(name)和专业(profession),
–并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中
--A. 声明游标,存储查询结果表
--B. 准备:创建表结构
--C. 开启游标
--D. 获取游标中的记录
--E. 插入数据到新表中
--F. 关闭游标
create procedure p11(in uage int)
begin
#游标声明和普通变量声明是有先后顺序的,要先声明普通变量,再声明游标
declare uname varchar(100);
declare upeo varchar(100);
#创建游标
declare u_cursor cursor for select name,professio from tb_user where age<=uage;
drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);
open u_cursor;
#循环获取游标中的数据
while true do
fetch u_cursor into uname,upro;
insert into tb_user_pro values(null,uname,upro); #将获取到的数据插入到新表中
end while;
close u_cursor; #关闭游标
end;
call p11(40);
#执行失败,因为循环体中条件为while true,也就意味着我们没有退出循环,会
#一直循环下去。并且在循环过程中,每循环一次就要从游标中获取他的下一条记
#录,当循环到最后一条之后,再进行下一次循环时,游标中已经没有数据,此时再
#去获取游标中的数据将会报错退出循环。为解决当前问题,需要条件处理程序。
4.2.9 条件处理程序
==条件处理程序(handler)==可以用来定义流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法:
declare handler_action handler for condition_value [,condition_value]... statement;
handler_action
continue:继续执行当前程序
exit:终止执行当前程序
condition_value
sqlstate sqlstate_value:状态码,如02000
sql warning:所有以01开头的sqlstate代码的简写
not found:所有以02开头的sqlstate代码的简写
sql exception:所有没有被sql warning或not found捕获的sqlstate代码的简写
--A. 声明游标,存储查询结果表
--B. 准备:创建表结构
--C. 开启游标
--D. 获取游标中的记录
--E. 插入数据到新表中
--F. 关闭游标
create procedure p11(in uage int)
begin
#游标声明和普通变量声明是有先后顺序的,要先声明普通变量,再声明游标
declare uname varchar(100);
declare upeo varchar(100);
#创建游标
declare u_cursor cursor for select name,professio from tb_user where age<=uage;
#02000状态码表示抓取时没有数据。当下面循环体抓取不到数据报错时就会进入到这个条件处理程序,在条件处理程序当中他会关闭游标然后执行退出操作。
declare exit handler for SQLSTATE '02000' close u_cursor;
# 也可以使用: delclare exit handler for not found close u_cursor;
drop table if exists tb_user_pro;
create table if not exists tb_user_pro(
id int primary key auto_increment,
name varchar(100),
profession varchar(100)
);
open u_cursor;
#循环获取游标中的数据
while true do
fetch u_cursor into uname,upro;
insert into tb_user_pro values(null,uname,upro); #将获取到的数据插入到新表中
end while;
close u_cursor; #关闭游标
end;
call p11(30);
4.3 存储函数
存储函数是有返回值的存储过程,存储函数的参数只能是in类型的,具体语法如下:
create function 存储函数名称([参数列表)]
returns type [characteristic...]
begin
--SQL语句
return...;
end;
characteristic说明:
deterministic:相同的输入参数总是产生相同的结果。
no sql:不包含SQL语句。
reads sql data:包含读取数据的语句,但不包含写入数据的语句。
示例:
--存储函数
--从1累加到n
create function fun1(n int)
returns int deterministic
begin
declare total int default 0;
while n>0 do
set total := total + n;
set n := n - 1;
end while;
return total;
end;
select fun1(100);
弊端:必须得有返回值,因此能够使用存储过程替代就使用存储过程。
4.4 触发器
4.4.1 介绍
触发器是与表有关的数据库对象,指在insert/update/delete之前或之后,出发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。
使用别名old和new来引用触发器中变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发器,不支持语句机触发器。
触发器类型 | new 和 old |
---|---|
insert型触发器 | new表示将要或者已经新增的数据 |
update型触发器 | old表示修改之前的数据,new表示将要或已经修改后的数据 |
delete型触发器 | old表示将要或者已经删除的数据 |
4.4.2 语法
- 创建
create trigger trigger_name
before/after insert/update/delete
on table_name for each row --行级触发器
begin
trigger_stmt;
end;
- 查看
show triggers;
- 删除
drop trigger [schema_name.]trigger_name; --如果没有指定chema_name,默认为当前数据库
- 示例
(1)insert型触发器
首先将SQL语句的结束符定义为$$,然后创建学生表student的触发器intrigger,当插入数据后,触发器将插入的学生姓名存储在stu_name当中。由图中所示,当插入数据后,能查询到插入的学生姓名。
(2)update型触发器
已知学生表,创建student表的update型触发器,当更新表中学生名字后,存储学生原来的名字和更新后的姓名到old_name和new_name。当对表中某个学生姓名进行更改后,可以查询到他的新名和旧名。
(3)delete型触发器
创建学生表的delete型触发器,当删除某个学生时,存储要删除的学生的姓名。如图中所示,当删除名为’WWC’的学生后,我们可以查询到被删除的学生的姓名。