(6)存储过程(Stored Procedure)
是一组为了完成特定功能的SQL语句集,是利用SQL Server所提供的Transact-SQL语言所编写的程序。经编译后存储在数据库中。存储过程是数据库中的一个重要对象,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是由流控制和SQL语句书写的过程,这个过程经编译和优化后存储在数据库服务器中,存储过程可由应用程序通过一个调用来执行,而且允许用户声明变量。同时,存储过程可以接收和输出参数、返回执行存储过程的状态值,也可以嵌套调用。
为什么要使用存储过程:
ü 存储过程是已经被认证的技术!
ü 存储过程会使系统运行更快!
ü 存储过程是可复用的组件!它是数据库逻辑而不是应用程序。
ü 存储过程将被保存!
存储过程的优点:
存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般SQL语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度。
当对数据库进行复杂操作时(如对多个表进行Update、Insert、Query、Delete时),可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。
存储过程可以重复使用,可减少数据库开发人员的工作量。
安全性高,可设定只有某此用户才具有对指定存储过程的使用权。
存储过程与函数的区别:
自定义函数有且只有一个返回值,就像普通的函数一样,可以直接在表达式中嵌入调用。存储过程可以没有返回值,也可以有任意个输出参数,必须单独调用。
执行的本质都一样。只是函数有如只能返回一个变量的限制。而存储过程可以返回多个。而函数是可以嵌入在sql中使用的,可以在select中调用,而存储过程不行。
函数限制比较多,比如不能用临时表,只能用表变量。还有一些函数都不可用等等。而存储过程的限制相对就比较少
函数限制比较多,比如不能用临时表,只能用表变量。还有一些函数都不可用等等。而存储过程的限制相对就比较少
一般来说,存储过程实现的功能要复杂一点,而函数的实现的功能针对性比较强。对于存储过程来说可以返回参数,而函数只能返回值或者表对象。
存储过程一般是作为一个独立的部分来执行,而函数可以作为查询语句的一个部分来调用,由于函数可以返回一个表对象,因此它可以在查询语句中位于FROM关键字的后面。
创建存储过程:
CREATE
[DEFINER = { user | CURRENT_USER }]
PROCEDURE sp_name ([proc_parameter[,...]])
[characteristic ...] routine_body
说明:
ü DEFINER:指明使用存储过程的访问权限。
ü sp_name: 存储过程名称。
ü proc_parameter: [ IN | OUT | INOUT ] param_name type
in:表示向存储过程中传入参数;存储过程默认为传入参数,所以参数in可以省略;
out:表示向外传出参数;
inout:表示定义的参数可传入存储过程,并可以被存储过程修改后传出存储过程;
param_name:参数名;
type:参数的类型,可以为mysql任何合法得数据类型。
如果有多个参数,参数之间可以用逗号进行分割。
ü Characteristic:
LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
这个LANGUAGE SQL子句是没有作用的。仅仅是为了说明下面过程的主体使用SQL语言编写。这条是系统默认的。
如果程序或线程总是对同样的输入参数产生同样的结果,则被认为它是“确定的”( DETERMINISTIC),否则就是“非确定”的。默认的就是NOT DETERMINISTIC。
CONTAINS SQL表示子程序不包含读或写数据的语句。
NO SQL表示子程序不包含SQL语句。
READS SQL DATA表示子程序包含读数据的语句,但不包含写数据的语句。
MODIFIES SQL DATA表示子程序包含写数据的语句。如果这些特征没有明确给定,默认的是CONTAINS SQL。
SQL SECURITY特征可以用来指定子程序该用创建子程序者的许可来执行,还是使用调用者的许可来执行。默认值是DEFINER。
COMMENT子句是一个MySQL的扩展,它可以被用来描述存储程序。这个信息被SHOW CREATE PROCEDURE和 SHOW CREATE FUNCTION语句来显示。存储子程序不能使用LOAD DATA INFILE。
特征子句也有默认值,如果省略了就相当于:LANGUAGE SQL NOT DETERMINISTIC SQL SECURITY DEFINER COMMENT ''
ü routine_body:包含合法的SQL过程语句。可以使用复合语句语法, 复合语句可以包含声明,循环和其它控制结构语句。
修改存储过程:
ALTER {PROCEDURE} sp_name [characteristic ...]
说明:
ü 这个语句可以被用来改变一个存储程序的特征。必须用ALTER ROUTINE权限才可用此子程序。这个权限被自动授予子程序的创建者。
ü 在ALTER PROCEDURE语句中,可以指定超过一个的改变。
删除存储过程:
DROP {PROCEDURE | FUNCTION} [IF EXISTS] sp_name
不能在一个存储过程中删除另一个存储过程,只能调用另一个存储过程
显示存储过程:
SHOW CREATE {PROCEDURE} sp_name
似于SHOW CREATE TABLE,它返回一个可用来重新创建已命名子程序的确切字符串。
显示存储过程特征:
SHOW {PROCEDURE} STATUS [LIKE 'pattern']
它返回子程序的特征,如数据库,名字,类型,创建者及创建和修改日期。
调用存储过程:
CALL sp_name([parameter[,...]])
调用一个先前用CREATE PROCEDURE创建的程序。
CALL语句可以用声明为OUT或的INOUT参数的参数给它的调用者传回值。它也“返回”受影响的行数,客户端程序可以在SQL级别通过调用ROW_COUNT()函数获得这个数,从C中是调用the mysql_affected_rows() C API函数来获得。
存储过程实例(基本的创建、调用、删除语法):
delimiter //
DROP PROCEDURE IF EXISTS test // 如果存在test则删除
CREATE PROCEDURE test /* 存储过程名 */
(IN inparms INT, OUT outparams varchar(32)) /* 输入、输出参数 */
BEGIN /* 语句块头 */
DECLARE var CHAR(10); /* 变量声明 */
IF inparms = 1 THEN /* IF条件开始*/
SET var = 'hello'; /* 赋值 */
ELSE
SET var = 'world';
END IF; /* IF结束 */
INSERT INTO t1 VALUES (var); /* SQL语句 */
SELECT name FROM t1 LIMIT 1 INTO outparams;
END
//
delimiter ;
call test(1, @out); /*调用存储过程*/
存储过程的变量:
l 声明变量:
DECLARE var_name[,...] type [DEFAULT value]
这个语句被用来声明局部变量。要给变量提供一个默认值,需要包含一个DEFAULT子句。值可以被指定为一个表达式,不需要为一个常数。如果没有DEFAULT子句,初始值为NULL。局部变量的作用范围在它被声明的BEGIN ... END块内。它可以被用在嵌套的块中,除了那些用相同名字声明变量的块。
l 变量赋值,SET语句:
SET var_name = expr [, var_name = expr] ...
也可以用语句代替SET来为用户变量分配一个值。在这种情况下,分配符必须为:=而不能用=,因为在非SET语句中=被视为一个比较操作符,如下所示:
mysql> SET @t1=0, @t2=0, @t3=0;
mysql> SELECT @t1:=0,@t2:=0,@t3:=0;
对于使用select语句为变量赋值的情况,若返回结果为空,即没有记录,此时变量的值为上一次变量赋值时的值,如果没有对变量赋过值,则为NULL。
l 变量赋值,SELECT ... INTO语句
SELECT col_name[,...] INTO var_name[,...] table_expr
这个SELECT语法把选定的列直接存储到变量。因此,只有单一的行可以被取回。
SELECT id,data INTO x,y FROM test.t1 LIMIT 1;
存储过程的语句:
l BEGIN...END复合语句
[begin_label:] BEGIN
[statement_list]
END [end_label]
存储子程序可以使用BEGIN ... END复合语句来包含多个语句。statement_list 代表一个或多个语句的列表。statement_list之内每个语句都必须用分号(;)来结尾。复合语句可以被标记。除非begin_label存在,否则end_label不能被给出,并且如果二者都存在,他们必须是同样的
l 流程控制结构
IF语句
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]
END IF
statement_list可以包括一个或多个语句。
举例:
DELIMITER //
CREATE PROCEDURE p1(IN parameter1 INT)
BEGIN
DECLARE variable1 INT;
SET variable1 = parameter1 + 1;
IF variable1 = 0 THEN
INSERT INTO t VALUES (17);
END IF;
IF parameter1 = 0 THEN
UPDATE t SET s1 = s1 + 1;
ELSE
UPDATE t SET s1 = s1 + 2;
END IF;
END; //
DELIMITER ;
CASE语句
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
Or:
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE
举例:
CREATE PROCEDURE p2 (IN parameter1 INT)
BEGIN
DECLARE variable1 INT;
SET variable1 = parameter1 + 1;
CASE variable1
WHEN 0 THEN INSERT INTO t VALUES (17);
WHEN 1 THEN INSERT INTO t VALUES (18);
ELSE INSERT INTO t VALUES (19);
END CASE;
END; //
循环语句
WHILE … END WHILE
LOOP … END LOOP
REPEAT … END REPEAT
GOTO
前三种是标准的循环方式,至于GOTO就如C语言里的GOTO一样,尽量少用!
在循环中还穿插一些循环控制语句,如LEAVE(类似C语言的break)、ITERATE(类似C语言的continue)等。
LEAVE语句
LEAVE label 这个语句被用来退出任何被标注的流程控制构造。它和BEGIN ... END或循环一起被使用。
ITERATE语句
ITERATE label ITERATE只可以出现在LOOP, REPEAT, 和WHILE语句内。ITERATE意思为:再次循环。
WHILE … END WHILE 举例:
CREATE PROCEDURE p4 ()
BEGIN
DECLARE v INT;
SET v = 0;
WHILE v < 5 DO
INSERT INTO t VALUES (v);
SET v = v + 1;
END WHILE;
END; //
LOOP … END LOOP 举例:
CREATE PROCEDURE p5 ()
BEGIN
DECLARE v INT;
SET v = 0;
loop_label: LOOP
INSERT INTO t VALUES (v);
SET v = v + 1;
IF v >= 5 THEN
LEAVE loop_label;
END IF;
END LOOP;
END; //
[begin_label:] LOOP
statement_list
END LOOP [end_label]
LOOP允许某特定语句或语句群的重复执行,实现一个简单的循环构造。在循环内的语句一直重复直到循环被退出,退出通常伴随着一个LEAVE 语句。
REPEAT … END REPEAT 举例:
CREATE PROCEDURE p6 ()
BEGIN
DECLARE v INT;
SET v = 0;
REPEAT
INSERT INTO t VALUES (v);
SET v = v + 1;
UNTIL v >= 5 END REPEAT;
END; //
迭代(ITERATE)语句
CREATE PROCEDURE p7 ()
BEGIN
DECLARE v INT;
SET v = 0;
loop_label: LOOP
IF v = 3 THEN
SET v = v + 1;
ITERATE loop_label;
END IF;
INSERT INTO t VALUES (v);
SET v = v + 1;
IF v >= 5 THEN
LEAVE loop_label;
END IF;
END LOOP;
END; //
存储过程的注释语法:
mysql存储过程可使用两种风格的注释
双模杠:--,该风格一般用于单行注释
c风格:/* 注释内容 */,一般用于多行注释
存储过程的条件和异常处理程序:
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
这个语句指定每个可以处理一个或多个条件的处理程序。如果产生一个或多个条件,指定的语句被执行。
对一个CONTINUE处理程序,当前子程序的执行在执行处理程序语句之后继续。对于EXIT处理程序,当前BEGIN...END复合语句的执行被终止。UNDO 处理程序类型语句还不被支持。
SQLWARNING是对所有以01开头的SQLSTATE代码的速记。
NOT FOUND是对所有以02开头的SQLSTATE代码的速记。
SQLEXCEPTION是对所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE代码的速记。
声明自定义条件:
DECLARE condition_name CONDITION FOR condition_value
condition_value:
SQLSTATE [VALUE] sqlstate_value
举例:
CREATE TABLE test.t (s1 int,primary key (s1));
delimiter //
CREATE PROCEDURE handlerdemo ()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1;
SET @x = 1;
INSERT INTO test.t VALUES (1);
SET @x = 2;
INSERT INTO test.t VALUES (1);
SET @x = 3;
END;//
delimiter ;
存储过程综合实例:(包含事务,参数,嵌套调用,游标,循环等)
drop procedure if exists pro_rep_shadow_rs;
delimiter |
----------------------------------
-- rep_shadow_rs
-- 用来处理信息的增加,更新和删除
-- 每次只更新上次以来没有做过的数据
-- 根据不同的标志位
-- 需要一个输出的参数,
-- 如果返回为0,则调用失败,事务回滚
-- 如果返回为1,调用成功,事务提交
--
-- 测试方法
-- call pro_rep_shadow_rs(@rtn);
-- select @rtn;
----------------------------------
create procedure pro_rep_shadow_rs(out rtn int)
begin
-- 声明变量,所有的声明必须在非声明的语句前面
declare iLast_rep_sync_id int default -1;
declare iMax_rep_sync_id int default -1;
-- 如果出现异常,或自动处理并rollback,但不再通知调用方了
-- 如果希望应用获得异常,需要将下面这一句,以及启动事务和提交事务的语句全部去掉
declare exit handler for sqlexception rollback;
-- 查找上一次的
select eid into iLast_rep_sync_id from rep_de_proc_log where tbl='rep_shadow_rs';
-- 如果不存在,则增加一行
if iLast_rep_sync_id=-1 then
insert into rep_de_proc_log(rid,eid,tbl) values(0,0,'rep_shadow_rs');
set iLast_rep_sync_id = 0;
end if;
-- 下一个数字
set iLast_rep_sync_id=iLast_rep_sync_id+1;
-- 设置默认的返回值为0:失败
set rtn=0;
-- 启动事务
start transaction;
-- 查找最大编号
select max(rep_sync_id) into iMax_rep_sync_id from rep_shadow_rs;
-- 有新数据
if iMax_rep_sync_id>=iLast_rep_sync_id then
-- 调用
call pro_rep_shadow_rs_do(iLast_rep_sync_id,iMax_rep_sync_id);
-- 更新日志
update rep_de_proc_log set rid=iLast_rep_sync_id,eid=iMax_rep_sync_id where tbl='rep_shadow_rs';
end if;
-- 运行没有异常,提交事务
commit;
-- 设置返回值为1
set rtn=1;
end;
|
delimiter ;
drop procedure if exists pro_rep_shadow_rs_do;
delimiter |
---------------------------------
-- 处理指定编号范围内的数据
-- 需要输入2个参数
-- last_rep_sync_id 是编号的最小值
-- max_rep_sync_id 是编号的最大值
-- 无返回值
---------------------------------
create procedure pro_rep_shadow_rs_do(last_rep_sync_id int, max_rep_sync_id int)
begin
declare iRep_operationtype varchar(1);
declare iRep_status varchar(1);
declare iRep_Sync_id int;
declare iId int;
-- 这个用于处理游标到达最后一行的情况
declare stop int default 0;
-- 声明游标
declare cur cursor for select id,Rep_operationtype,iRep_status,rep_sync_id from rep_shadow_rs where rep_sync_id between last_rep_sync_id and max_rep_sync_id;
-- 声明游标的异常处理,设置一个终止标记
declare CONTINUE HANDLER FOR SQLSTATE '02000' SET stop=1;
-- 打开游标
open cur;
-- 读取一行数据到变量
fetch cur into iId,iRep_operationtype,iRep_status,iRep_Sync_id;
-- 这个就是判断是否游标已经到达了最后
while stop <> 1 do
-- 各种判断
if iRep_operationtype='I' then
insert into rs0811 (id,fnbm) select id,fnbm from rep_shadow_rs where rep_sync_id=iRep_sync_id;
elseif iRep_operationtype='U' then
begin
if iRep_status='A' then
insert into rs0811 (id,fnbm) select id,fnbm from rep_shadow_rs where rep_sync_id=iRep_sync_id;
elseif iRep_status='B' then
delete from rs0811 where id=iId;
end if;
end;
elseif iRep_operationtype='D' then
delete from rs0811 where id=iId;
end if;
-- 读取下一行的数据
fetch cur into iId,iRep_operationtype,iRep_status,iRep_Sync_id;
end while; -- 循环结束
close cur; -- 关闭游标
end;
|
存储过程操作符:
l 算术运算符
+ 加 SET var1=2+2; 4
- 减 SET var2=3-2; 1
* 乘 SET var3=3*2; 6
/ 除 SET var4=10/3; 3.3333
DIV 整除 SET var5=10 DIV 3; 3
% 取模 SET var6=10%3 ; 1
l 比较运算符
> 大于 1>2 False
< 小于 2<1 False
<= 小于等于 2<=2 True
>= 大于等于 3>=2 True
BETWEEN 在两值之间 5 BETWEEN 1 AND 10 True
NOT BETWEEN 不在两值之间 5 NOT BETWEEN 1 AND 10 False
IN 在集合中 5 IN (1,2,3,4) False
NOT IN 不在集合中 5 NOT IN (1,2,3,4) True
= 等于 2=3 False
<>, != 不等于 2<>3 False
<=> 严格比较两个NULL值是否相等 NULL<=>NULL True
LIKE 简单模式匹配 "Guy Harrison" LIKE "Guy%" True
REGEXP 正则式匹配 "Guy Harrison" REGEXP "[Gg]reg" False
IS NULL 为空 0 IS NULL False
IS NOT NULL 不为空 0 IS NOT NULL True
l 逻辑运算符
与(AND)
AND |
TRUE |
FALSE |
NULL |
TRUE |
TRUE |
FALSE |
NULL |
FALSE |
FALSE |
FALSE |
NULL |
NULL |
NULL |
NULL |
NULL |
或(OR)
OR |
TRUE |
FALSE |
NULL |
TRUE |
TRUE |
TRUE |
TRUE |
FALSE |
TRUE |
FALSE |
NULL |
NULL |
TRUE |
NULL |
NULL |
异或(XOR)
XOR |
TRUE |
FALSE |
NULL |
TRUE |
FALSE |
TRUE |
NULL |
FALSE |
TRUE |
FALSE |
NULL |
NULL |
NULL |
NULL |
NULL |
l 位运算符
| 位或
& 位与
<< 左移位
>> 右移位
~ 位非(单目运算,按位取反)
存储过程基本函数:
mysq存储过程中常用的函数,字符串类型操作,数学类,日期时间类
l 字符串类
CHARSET(str) //返回字串字符集
CONCAT (string2 [,... ]) //连接字串
INSTR (string ,substring )//返回substring首次在string中出现的位置,不存在返回0
LCASE (string2 ) //转换成小写
LEFT (string2 ,length ) //从string2中的左边起取length个字符
LENGTH (string ) //string长度
LOAD_FILE (file_name ) //从文件读取内容
LOCATE (substring , string [,start_position ] ) 同INSTR,但可指定开始位置
LPAD (string2 ,length ,pad ) //重复用pad加在string开头,直到字串长度为length
LTRIM (string2 ) //去除前端空格
REPEAT (string2 ,count ) //重复count次
REPLACE (str ,search_str ,replace_str ) //在str中用replace_str替换search_str
RPAD (string2 ,length ,pad) //在str后用pad补充,直到长度为length
RTRIM (string2 ) //去除后端空格
STRCMP (string1 ,string2 ) //逐字符比较两字串大小,
SUBSTRING (str , position [,length ]) //从str的position开始,取length个字符,
注:mysql中处理字符串时,默认第一个字符下标为1,即参数position必须大于等于1
mysql> select substring(’abcd’,0,2);
+―――――――�C+
| substring(’abcd’,0,2) |
+―――――――�C+
| |
+―――――――�C+
1 row in set (0.00 sec)
mysql> select substring(’abcd’,1,2);
+―――――――�C+
| substring(’abcd’,1,2) |
+―――――――�C+
| ab |
+―――――――�C+
1 row in set (0.02 sec)
TRIM([[BOTH|LEADING|TRAILING] [padding] FROM]string2) //去除指定位置的指定字符
UCASE (string2 ) //转换成大写
RIGHT(string2,length) //取string2最后length个字符
SPACE(count) //生成count个空格
l 数学类
ABS (number2 ) //绝对值
BIN (decimal_number ) //十进制转二进制
CEILING (number2 ) //向上取整
CONV(number2,from_base,to_base) //进制转换
FLOOR (number2 ) //向下取整
FORMAT (number,decimal_places ) //保留小数位数
HEX (DecimalNumber ) //转十六进制
注:HEX()中可传入字符串,则返回其ASC-11码,如HEX(’DEF’)返回4142143
也可以传入十进制整数,返回其十六进制编码,如HEX(25)返回19
LEAST (number , number2 [,..]) //求最小值
MOD (numerator ,denominator ) //求余
POWER (number ,power ) //求指数
RAND([seed]) //随机数
ROUND (number [,decimals ]) //四舍五入,decimals为小数位数]
注:返回类型并非均为整数,如:
(1)默认变为整形值
mysql> select round(1.23);
+――――-+
| round(1.23) |
+――――-+
| 1 |
+――――-+
1 row in set (0.00 sec)
mysql> select round(1.56);
+――――-+
| round(1.56) |
+――――-+
| 2 |
+――――-+
1 row in set (0.00 sec)
(2)可以设定小数位数,返回浮点型数据
mysql> select round(1.567,2);
+―――――-+
| round(1.567,2) |
+―――――-+
| 1.57 |
+―――――-+
1 row in set (0.00 sec)
SIGN (number2 ) //返回符号,正负或0
SQRT(number2) //开平方
l 日期时间类
ADDTIME (date2 ,time_interval ) //将time_interval加到date2
CONVERT_TZ (datetime2 ,fromTZ ,toTZ ) //转换时区
CURRENT_DATE ( ) //当前日期
CURRENT_TIME ( ) //当前时间
CURRENT_TIMESTAMP ( ) //当前时间戳
DATE (datetime ) //返回datetime的日期部分
DATE_ADD (date2 , INTERVAL d_value d_type ) //在date2中加上日期或时间
DATE_FORMAT (datetime ,FormatCodes ) //使用formatcodes格式显示datetime
DATE_SUB (date2 , INTERVAL d_value d_type ) //在date2上减去一个时间
DATEDIFF (date1 ,date2 ) //两个日期差
DAY (date ) //返回日期的天
DAYNAME (date ) //英文星期
DAYOFWEEK (date ) //星期(1-7) ,1为星期天
DAYOFYEAR (date ) //一年中的第几天
EXTRACT (interval_name FROM date ) //从date中提取日期的指定部分
MAKEDATE (year ,day ) //给出年及年中的第几天,生成日期串
MAKETIME (hour ,minute ,second ) //生成时间串
MONTHNAME (date ) //英文月份名
NOW ( ) //当前时间
SEC_TO_TIME (seconds ) //秒数转成时间
STR_TO_DATE (string ,format ) //字串转成时间,以format格式显示
TIMEDIFF (datetime1 ,datetime2 ) //两个时间差
TIME_TO_SEC (time ) //时间转秒数]
WEEK (date_time [,start_of_week ]) //第几周
YEAR (datetime ) //年份
DAYOFMONTH(datetime) //月的第几天
HOUR(datetime) //小时
LAST_DAY(date) //date的月的最后日期
MICROSECOND(datetime) //微秒
MONTH(datetime) //月
MINUTE(datetime) //分
附:可用在INTERVAL中的类型
DAY ,DAY_HOUR ,DAY_MINUTE ,DAY_SECOND ,HOUR ,HOUR_MINUTE ,HOUR_SECOND ,MINUTE ,MINUTE_SECOND,MONTH ,SECOND ,YEAR