数据库入门(MySQL):视图、存储过程、触发器
- 视图的操作
- 存储过程与存储函数
- 触发器的操作
- (最末尾有测试数据库表和数据代码)
一、视图的操作
1.1视图可以用来做什么?
简单的说视图就是一个查询,它在业务中只需要最简单的查询语句就可以获取到业务需要的数据。
select * from view_selectproduct;
可以这么简单的查询数据记录,是通过将SQL查询语句封装在视图中,比如我们来看看如何创建视图:
create view view_selectproduct
as
select id,name
from t_product;
1.2为什么使用视图?
前面说过了在业务开发中不需要每次都去写复杂的查询语句,只要使用最简单的查询语句查询视图就可以实现(便捷)。除了方便开发,还有一些数据表中除了必要的业务需要的数据,还有一些敏感数据并不需要查询出来,比如查看用户信息并不需要将用户的密码查询出来(数据安全)。还可以为不同的业务定制不同的查询视图(灵活)。
1.3视图的一些概念:
- 视图是基于数据表的虚拟表,它并不用于存储数据,是表的抽象和在逻辑意义上建立的新关系。
- 视图的列可以来自于不同的表。(根据封装的SQL查询语句产生)
- 视图的建立与删除不影响基本表。
- 对视图内容的更新(添加、删除、修改)之影响视图的查询结果,并不影响数据表的结构。
1.4查看、修改、删除视图的操作:
//查看视图定义信息 show create view view_name; //查看视图设计信息(describe | desc) desc view_name; //修改视图:CREATE OR REPLACE VIEW ... AS ...; create or replace view view_name as select...; //修改视图:ALTER VIEW ... AS ...; alter view view_name as select...; //删除视图 drop view view_name [,view_name]...;
1.5视图除了查询数据记录还能做些什么?
视图也可以用来进行数据添加、删除,且直接影响基本表。但是当视图来自多个基础表时,不允许添加和删除数据。虽然视图可以添加和删除数据,但是不建议这么做。
//通过视图写入数据记录 INSERT INTO view_name (field1,field2,...fieldn) VALUES(data1,data2,...datan); //通过视图删除数据记录 DELECT FROM view_name WHERE condition
1.6可以通过视图来创建常量视图:
//∏ CREATE VIEW pi AS SELECT 3.1415926;
二、存储过程与存储函数
1.存储过程可以用来做什么?
在很多的实际业务中,一个请求需要多条SQL语句来完成,这时候就需要用到存储过程,也就是说存储过程是用来处理多条SQL语句的请求。例如为了完成商品订单处理,需要考虑以下情况:
(a)在生成订单前,首先需要查看商品库中是否有相应的商品(是否还有库存)。
(b)如果商品库中存在相应的商品,接着需要预定商品以防该商品卖给他人,并且修改库存物品数量以反映正确的库存量。
(c)如果商品库存中不存在相应的商品,则需要向供货商订货。
在以上示例中,它肯定不是单条SQL语句能实现的,这时候就需要使用存储过程来实现一次处理多条SQL语句的操作。
2.存储过程的语法:
CREATE PROCEDURE procedure_name([procedure_parameter[,...]])
[characteristic...] routine_body
- procedure_name:存储过程的名称。
- procedure_parameter:存储过程的参数,每个参数的语法形式 [IN|OUT|INOUT] parameter_name type ,参数三部分分别表示:输入输出类型、参数名、参数类型。IN表示输入类型;OUT表示输出类型;INOUT表示输入/输出类型。
- characteristic:存储过程的特性。
- routine_body:存储过程的SQL语句代码。
存储过程的特性(characteristic)参数取值解析,以及默认取值:
LANGUAGE SQL//指定存储过程routine_body部分为SQL语句,默认特性。(看到有些资料中说将来可能支持其他语言来实现存储过程的数据操作) | [NOT] DETERMINISTIC//表示存储过程的执行结果是否确定,意思是输入相同的参数执行结果是否一致,默认DETERMINISTIC表示存储过程执行结果一致。那NOT DETERMINISTIC也就是执行结果不一致,这个特性简单的来讲可以在结果一致时缓存执行结果,不需要每次调用存储过程都操作数据库,从而达到提升性能的目的 | {CONTAINS SQL|NO SQL|READS SQL DATA|MODIFIES SQL DATA}//表示使用SQL语句的限制,默认值为CONTAINS SQL,表示包含SQL语句但不包含读或写的语句。NO SQL表示不包含SQL语句;READS SQL DATA表示包含读数据的语句;MODIFIES SQL DATA表示包含写数据的语句。看到这个解析是不是很恐慌,别怕这个特性目前只提交给服务,并不约束实际的实际的数据操作,所以说这个特性实际上不会被作用,至少目前是不会作用。 | SQL SECURITY {DEFINER|INVOKER}//设置谁有权限来执行,默认值为DEFINER,表示只有自己才能够执行。INVOKER表示调用者可以执行。 | COMMENT 'string'//表示注释语句,用来描述存储过程的字符串。
看到上面的语法和特性解析,是不是感觉存储过程就弄明白了,理论上这么说没错,但是别飘!比如在SQL语句中使用分号(;)表示SQL语句的结束符,我们知道每写一句SQL语句就需要使用分号间隔,但是一个存储过程中需要包含多个SQL语句,那用什么来间隔表示一个独立的存储过程语句呢?
//在存储过程语句开始前将接受符号修改成“$$”,在存储过程语句结束再修改成分号(;) DELIMITER $$ ...存储过程语句 DELIMITER ;
除了结束符号的问题,还有参数写在括号内后面紧接着写特性,写完特性就写SQL语句?理论上还是不错,但是真的是这样吗?要是这样存储过程在服务内岂不是还需要进行一个复杂的语法分析?这种损耗性能的行为怎么可能出现在数据库程序中,在存储过程中将SQL语句使用BEGIN ... END包裹起来,这就好比一对标签告诉服务这是SQL语句。来看看下面这个简单的示例:
#创建存储过程 delimiter $$ create procedure proce_employee_sal(in comparison float) comment '查询所有雇员工资' begin select sal from t_employee where sal>comparison; end$$ delimiter ; #执行存储过程 call proce_employee_sal(1800);
3.存储函数
存储函数与存储过程基本类似,都是用来处理一组SQL语句的数据操作,但两者的用途在适应场景上有些区别,通常查询一组数据使用存储过程(一组数据存储函数无法返回,最后返回的是空),查询单个数据使用存储函数(这里使用返回数据比查询应该更合理一点),为什么有这样的区别这里不深入解析,这涉及的内容不是一两句讲的清楚的,主要是我现在也将不明白。。。皮一下,淡定,但可以肯定的是这么区分用途归根到底还是性能问题。用途上有一些差异,就必然会在语法结构上体现出来,下面来看看存储函数的语法:
CREATE FUNCTION function_name([function_parameter[,...]])
[characteristic...] routine_body
从简洁的语法上来看存储函数与存储过程基本没有区别,但是存储函数的参数function_parameter有一些区别:
//存储函数参数的语法 parameter_name type
与存储过程对比存储函数不需要标注参数的输入输出,只需要写参数名称和类型,这是因为存储函数使用return来返回(输出)数据。虽然没有参数类型,但是需要在特性内写返回值的数据类型(RETURNS type)。然后就是执行回调函数使用的关键字不是call,而是select。看下面这个简单的示例:
#创建存储函数 delimiter $$ drop function if exists func_employee_sal; create function func_employee_sal() returns float comment '查询雇员工资' begin return (select sal from t_employee where ename="SMITH"); end $$ delimiter ; #执行存储函数 select func_employee_sal();
4.关于存储过程和函数的表达式
在一开始就说过存储过程是用来处理多条SQL语句的,在存储过程和存储函数内处理复杂的数据操作,还需要操作变量、操作游标、流程控制、临时表、操作条件。
4.1操作变量
#声明变量:var_name--变量名称、type--变量的类型、DEFAULT-默认值,如果没有该语句默认为null DECLARE var_name[,...] type [DEFAULT value] #手动赋值赋值变量:参数expr表示变量赋值的表达式 SET var_name=expr[,...] #通过查询数据赋给变量:在SELECT查询语句中使用INTO实现变量赋值 SELECT field INTO var_name [,...] ....
操作变量示例:
delimiter $$ drop procedure if exists procedure_cs; CREATE PROCEDURE procedure_cs() begin declare employee_sal float default 1000; declare num int ; select sal into employee_sal from t_employee where empno=7369; set num = 100; select employee_sal,num; end $$ delimiter ; call procedure_cs();
4.2流程控制
4.2.1:流程条件控制if else语法
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list]...
[ELSEIF search_condition]
END IF
流程条件控制if else示例:
drop procedure if exists procedure_lc_ie; delimiter $$ create procedure procedure_lc_ie(in num int,out val int) begin if num > 999 then set val = num; else set val = 999; end if ; end; delimiter ; call procedure_lc(1800,@lc_val); select @lc_val; #1800
4.2.2:流程条件控制case when语法
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list]
[ELSE statement_list]
END CASE
流程条件控制case when示例
drop function if exists func_lc_cw; delimiter $$ create function func_lc_cw(val varchar(16)) returns varchar(16) begin case val when '张三' then return (select "zhangsan"); when "李四" then return (select "lisi"); when "王五" then return (select "wangwu"); end case; end $$ delimiter ; select func_lc_cw('张三'); #zhangsan
4.2.3:流程循环控制-loop,注意使用loop需要使用leave来退出循环体
[begin_label:]LOOP statement_lit--这里是循环内容,在循环内部需要设置LEABE label退出循环 END LOOP [end_label]
流程循环控制-loop,注意使用loop示例
drop procedure if exists procedure_lc_loop; delimiter $$ create procedure procedure_lc_loop(out s_num int) begin declare num int default 0; cycle: loop if num<10 then set num=num+1; else set s_num=num; leave cycle; end if; end loop cycle; end $$ delimiter ; call procedure_lc_loop(@s_num); select @s_num;
4.2.4:流程循环控制-while,当满足条件时执行循环体
[begin_label:] WHILE search_condition DO
statement_list
END WHILE [end_label]
流程循环控制-while示例
drop function if exists func_lc_while; delimiter $$ create function func_lc_while() returns int begin declare num int default 0; while num < 10 do set num = num+1; end while ; return (select num); end $$ delimiter ; #测试 select func_lc_while();
4.2.5:流程循环控制-repeat,当满足条件时退出循环体
[begin_label:] REPEAT search_condition DO
statement_list
END REPEAT [end_label]
流程循环控制-repeat示例
drop procedure if exists procedure_lc_repeat; delimiter $$ create procedure procedure_lc_repeat() begin declare num int default 0; repeat set num = num + 1; until num > 9 end repeat ; select num; end $$ delimiter ; #测试 call procedure_lc_repeat();
4.3操作游标
在很多时候需查询多条记录,然后遍历这些记录,前面介绍过循环流程控制,但是在数据库中并没有提供像数组那样的迭代接口,而是提供了操作游标的API。
游标可以理解为下标,但它不能像下标那样具备索引功能,而是每调用一次从前到后遍历一条记录,所以就需要循环流程控制配合实现遍历,当然还少不了需要操作条件用来判断是否遍历到记录的最末尾,关于操作条件在下一小节介绍。
4.3.1:声明游标
DECLARE cursor_name CURSOR FOR select_statement;
#关键字DECLARE用来声明游标
#参数cursor_name表示声明的游标名
#参数select_statement表示SELECT语句
4.3.2:打开游标
OPEN cursor_name
4.3.3:使用游标
FETCH cursor_name INTO var_name [,var_name] ...
#上述语句表示将参数游标cursor_name中SELECT语句的执行结果保存到变量参数var_name中
#使用游标类似高级语言中的数组遍历,当第一次使用游标时,此时游标指向结果集的第一条记录
4.3.4:关闭游标
CLOSE cursor_name
游标示例:
1 #游标示例1--统计工资大于999的员工数 2 drop procedure if exists employee_count; 3 delimiter $$ 4 #创建存储过程 5 create procedure employee_count(out num int(11)) 6 comment '统计工资大于999的员工数' 7 begin 8 #声明变量 9 declare employee_sal float; 10 declare flag int; 11 #声明游标 12 declare cursor_employee cursor for select sal from t_employee; 13 declare continue handler for not found set flag=1; #当游标取不到数据时设置flag=1 14 #设置结束标志 15 set flag=0; 16 set num=0; 17 #打开游标 18 open cursor_employee; 19 #遍历游标指向的结果集 20 fetch cursor_employee into employee_sal; 21 while flag<>1 do 22 if employee_sal>999 then 23 set num=num+1; 24 end if; 25 fetch cursor_employee into employee_sal; 26 end while; 27 #关闭游标 28 close cursor_employee; 29 end $$ 30 delimiter ; 31 32 call employee_count(@count); 33 select @count; 34 35 #游标示例2--输出员工表中所有员工的姓名和工资 36 drop procedure if exists employee_name_sal; 37 delimiter $$ 38 create procedure employee_name_sal() 39 comment '输出员工表中所有员工的姓名和工资' 40 begin 41 #声明变量 42 declare flag int; 43 declare de_name varchar(32); 44 declare de_sal float; 45 #声明游标 46 declare cursor_e_name_sal cursor for select ename,sal from t_employee; 47 declare continue handler for not found set flag=1; 48 #设置结束标志 49 set flag=0; 50 #创建临时表 51 drop table if exists temporary_e_name_sal; 52 create temporary table if not exists temporary_e_name_sal(name varchar(32),sal float); 53 #打开游标 54 open cursor_e_name_sal; 55 #遍历游标指向的结果集 56 fetch cursor_e_name_sal into de_name,de_sal; 57 while flag<>1 do 58 if de_name is not null and de_sal is not null then 59 insert into temporary_e_name_sal(name,sal) values (de_name,de_sal); #将数据添加到临时表中 60 end if; 61 fetch cursor_e_name_sal into de_name,de_sal; 62 end while; 63 #关闭游标 64 close cursor_e_name_sal; 65 #从临时表中拿出结果集 66 select * from temporary_e_name_sal; 67 end $$ 68 delimiter ; 69 #执行存储过程 70 call employee_name_sal();
4.4操作条件(错误处理)
任何程序都是无法做到保证百分百不出问题,SQL中也是一样,同样SQL提供了处理程序:
DECLARE handler_type HANDLER FOR condition_value[, ...] sp_statement handler_type: CONTINUE |EXIT |UNDO condition_value: SQLSTATE[VALUE] sqlstate_value |condition_name |SQLWARNING |NOT FOUND |SQLEXCEPTION |mysql_error_code
handler_type表示当出现错误时处理的方式:CONTINUE-通过处理程序处理该错误以后继续后面的操作;EXIT-通过处理程序处理该错误以后终止后面的操作;UNDO-(文档中描述为还不被支持)。
condition_value表示处理错误的类型,也就是指定处理的错误类型:
SQLSTATE[value] sqlstate_value:指定错误状态码,比如出现表不存在时报的错误状态码是42S02 。
//当表达式中出现表不存在错误的错误时,给遍历err赋值:42S02 declare exit handler for sqlstate '42S02' begin set err='42S02' end;
condition_name:指定自定义的错误状态名称,这需要手动将错误类型封装到一个条件中,然后直使用这个错误状态别名,后面会有自定义条件的示例。
SQLWARNING:表示所有01开头的SQLSTATE代码(状态码)。
NOT FOUND:表示所有02开头的SQLSTATE代码(状态码)。
SQLEXCEPTION:表示没有被SQLWARNING、NOT FOUND捕获的错误代码,也就是除01、02开头的状态码。
操作条件示例:
#使用表不存在错误作为操作条件的示例 drop procedure if exists procedure_tj_b; delimiter $$ create procedure procedure_tj_b(out err varchar(16)) begin declare exit handler for sqlstate '42S02' begin set err="42S02"; end ; insert into a(a1,a2) values (1,2);#a这个表不存在报42S02错误。 end $$ delimiter ; call procedure_tj_b(@err); select @err;
自定义条件,也可以说是将指定的错误状态封装到一个条件中,在处理程序condition_value中直接使用这个条件就可以。语法:
DECLARE condition_name CONDITION FOR condition_value
condition_value;
SQLSTATE[value] sqlstate_value
|mysql_error_code
自定义条件示例:
#自定义错误(使用表不存在错误作为测试) drop procedure if exists procedure_tj_cs; delimiter $$ create procedure procedure_tj_cs(out err varchar(16)) begin declare tj condition for sqlstate '42S02'; declare exit handler for tj begin set err = '42S02'; select err; end ; insert into a(a1,a2) values (1,2); end $$ delimiter ; call procedure_tj_cs(@err); select @err;
关于更详细的错误处理可以参考这篇博客:https://www.cnblogs.com/yizitrd/p/5445071.html
5.修改存储过程与删除存储过程
一般情况下不会使用到修改,因为仅仅只能修改存储过程的特性,如果需要修改存储过程通常都是重新写一个同名的存储过程,在这个新的存储过程创建之前删除之前的存储过程,这样就达到了修改的目的。
修改存储过程语法:
ALTER PROCEDURE procedure_name
[characteristic...]
procedure_name:存储过程的名称。
characteristic:指定修改后存储过程的特性,与定义存储过程的特性参数对比,取值只能如下值:
|{CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA} |SQL SECURITY {DEFINER | INVOKER} |COMMENT 'string'
修改存储函数也是类似,只需要将procedure关键字改成function就可以了,可修改的特性值也是一样。
删除存储过程:
DROP PROCEDURE proce_name;//删除存储过程 DROP FUNCTION func_name;//删除存储函数
在删除的时候一般都会使用if exists判断是否存在该存储过程,保证删除操作不出错。删除之后可以通过系统表routines查询是否还存在该存储过程。
SELECT *
FROM ROUTINES
WHERE SPECIFIC_NAME='proce_name';
需要注意,ROUTINES表是系统数据库,在查询前注意切换到系统数据库 use information_schema; 。
三、触发器的操作
触发器用来监听一个数据表的insert、update、delete操作,并实现在这些操作之前或之后需要做的事情,看到有些资料中把它看作特殊的存储过程,如果从一个前端程序员的角度来说的话更感觉它就是数据库的事件操作。语法:
create trigger trigger_name BEFORE | AFTER trigger_EVENT ON TABLE_NAME FOR EACH ROW trigger_STMT
trigger_name:触发器名称。
BEFORE | AFTER :指定在监听操作前(BEFORE)触发还是之后(AFTER)触发。
trigger_EVENT:指定监听表的操作类型(INSERT、UPTATE、DELETE)。
TABLE_NAME:指定监听的数据表名称。
trigger_STMT:触发器的执行语句。注意触发器执行语句不能使用查询语句,触发器可以实现多条执行语句,使用begin end包裹。
delimiter $$ create trigger trigger_name BEFORE | AFTER trigger_EVENT ON TABLE_NAME FOR EACH ROW BEGIN trigger_STMT END delimiter ;
触发器示例:
1 #创建一条执行语句的触发器 2 drop trigger if exists trigger_testIn; 3 create trigger trigger_testIn 4 after insert on t_trigger for each row insert into test(str,inDate) values ("t_trigger_insert",now()); 5 6 insert into t_trigger(str) values ("测试触发器"); 7 select * from test; 8 9 #创建多条执行语句的触发器 10 drop trigger if exists trigger_testIns; 11 delimiter $$ 12 create trigger trigger_testIns 13 after insert on t_trigger for each row 14 begin 15 insert into test(str,inDate) values ("t_trigger_insert_1",now()); 16 insert into test(str,inDate) values ("t_trigger_insert_2",now()); 17 end $$ 18 delimiter ; 19 insert into t_trigger(str) values ("测试触发器"); 20 select * from test;
查看触发器:
show triggers ;#通过show指令查看触发器 select * from triggers;#在系统信息数据库information_schema中查看触发器 select * from triggers where trigger_name=trigger_name;
删除触发器操作在创建示例中已经有了。
~~示例测试使用的表和数据~~
1 create database test; 2 create table t_dept( 3 `deptno` int(11), 4 `dname` varchar(32), 5 `loc` varchar(32) 6 )engine=innoDB default charset=utf8mb4; 7 select * from t_dept; 8 insert into t_dept(deptno, dname, loc) values 9 (10,"ACCOUNTING","NEW YORK"), 10 (20,"RESEARCH","DALLAS"), 11 (30,"SALES","CHICAGO"), 12 (40,"OPERATIONS","BOSTON"); 13 14 create table t_employee( 15 `empno` int(11), 16 `ename` varchar(32), 17 `job` varchar(32), 18 `MGR` int(11), 19 `Hiredate` date, 20 `sal` float, 21 `comm` float, 22 `deptno` int(11) 23 )engine=InnoDB default charset=utf8mb4; 24 select * from t_employee; 25 insert into t_employee(empno, ename, job, MGR, Hiredate, sal, comm, deptno) 26 values (7369,"SMITH","CLERK",7902,'1981-03-12',800.00,null,20), 27 (7499,"ALLEN","SALESMAN",7698,"1982-03-12",1600.00,300,30), 28 (7521,"WARD","SALESMAN",7698,"1983-03-12",1250.00,500,30), 29 (7566,"JONES","MANAGER",7839,"1984-03-12",2975.00,null,20), 30 (7654,"MARTIN","SALESMAN",7698,"1985-03-12",1250.00,1400.00,30), 31 (7698,"BLAKE","MANAGER",7839,"1986-03-12",2850.00,null,30), 32 (7782,"CLARK","MANAGER",7839,"1987-03-12",2450.00,null,10), 33 (7788,"SCOTT","ANALYST",7566,"1988-03-12",3000.00,null,20), 34 (7839,"KING","PRESIDENT",null,"1989-03-12",5000.00,null,10), 35 (7844,"TURNER","SALESMAN",7698,"1990-03-12",1500.00,0.00,30), 36 (7876,"ADAMS","CLERK",7788,"1991-03-12",1100.00,null,20), 37 (7900,"JAMES","CLERK",7698,"1992-03-12",950.00,null,30), 38 (7902,"FORD","ANALYST",7566,"1993-03-12",3000.00,null,20), 39 (7934,"MILLER","CLERK",7782,"1994-03-12",1300.00,null,10);