存储过程和函数是事先经过编译存储在数据库中的一段 SQL语句的集合,调用存储过程和函数可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器中的传输,对于提高数据处理的效率是有好处的。
存储过程和函数的区别在于:函数必须有返回值,而存储过程没有,存储过程的参数可以使用 IN,OUT,INOUT 类型,而函数的参数只能是IN 类型的。如果有函数从其他类型的数据库迁移到 MySQL,那么就有可能因此需要将函数改造成存储过程。
在对存储过程或函数进行相关操作时,需要首先确认用户是否具有相关的权限。例如,创建存储过程或者函数需要 CREATE ROUTINE 权限。修改或者删除存储过程或者函数需要 ALTER ROUTINE 权限,执行存储过程或者函数需要 EXECUTE 权限。
CREATE PROCEDURE sp_name ([proc_parameter[,....]])
[characteristic...] routine_body
CREATE FUNCTION sp_name ([func_parameter[,....]])
RETURNS type
[characteristic...] routine_body
proc_parameter:
[IN | OUT | INOUT ] param_name type
func_parameter:
param_name type
type:
Any valid MySQL data type
characteristic:
LANQUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER}
| COMMENT 'string'
routine_body:
Valid SQL procedure statement or statements
ALTER { PROCEDURE | FUNCTION } sp_name [characteristic...]
characteristic:
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER}
| COMMENT 'string'
调用过程的语法如下:
CALL sp_name([parameter[,....]]);
MySQL 的存储过程和函数中允许包含 DDL 语句,也允许在存储过程中执行 提交 (Commit,即确认之前的修改)或者 回滚 (Rollback, 即放弃之前的修改),但是存储过程和函数中不允许执行 LOAD DATA INFILE 语句。此外,存储过程和函数中允许调用其它的过程或者函数。
下面创建了一个新的过程 proc_adder:
mysql > DELIMITER $$
mysql > CREATE PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int)
> BEGIN
> DECLARE c int;
> if a is null then
> set a = 0;
> end if;
> if b is null then
> set b = 0;
> end if;
> set sum = a + b;
> END $$
Query Ok, 0 rows affected (0.00 sec)
mysql >
mysql > DELIMITER ;
上面的存储过程比较简单做的是一个加法运算。
通常我们在执行创建过程和函数之前,都会通过 【DELIMITER $$ 】命令将语句的结束符由 【;】 修改成其它符号,这里使用的是 【$$】 ,这样在过程和函数中的【;】 就不会被 MySQL 解释成语句的结束而提示错误。在存储过程或者函数创建完毕,通过 【DELIMITER ;】 再将结束符改回成 【;】。
执行结果:
mysql> set @b=5;
Query OK, 0 rows affected (0.00 sec)
mysql> call proc_adder(2,@b,@s);
Query OK, 0 rows affected (0.00 sec)
mysql> select @s as sum;
+------+
| sum |
+------+
| 7 |
+------+
1 row in set (0.00 sec)
mysql>
可以看到,调用存储过程与直接执行SQL语句的效果是相同的,但是存储过的好处在于处理逻辑都封装在数据库端,调用值不需要了解中间的处理逻辑,一旦处理逻辑发生变化,只需要修改存储过程即可,而对调用者的程序完全没有影响。
另外,和视图的创建语法稍有不同,存储过程和函数的 CREATE 语法不支持用 CRATE OR REPLACE 对存储过程和函数进行修改,如果需要对已有的存储过程和函数进行修改,需要执行ALTER 语法。
下面对 characteristic 特征值的部分进行简单说明:
一次只能删除一个存储过程或者函数,删除存储过程或者函数要有该过程或者函数的 ALTER ROUTINE权限,具体语法如下:
DROP { PROCEDURE | FOUNCTION }{IF EXISTS} sp_name;
show { PROCEDURE | FOUNCTION } STATUS [like 'pattern'];
查看存储过程 proc_adder 的信息:
mysql> SHOW PROCEDURE STATUS LIKE 'proc_adder'\G;
*************************** 1. row ***************************
Db: daicooper
Name: proc_adder
Type: PROCEDURE
Definer: [email protected]
Modified: 2018-12-06 14:07:52
Created: 2018-12-06 14:07:52
Security_type: DEFINER
Comment:
character_set_client: utf8mb4
collation_connection: utf8mb4_unicode_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)
ERROR:
No query specified
mysql>
show CREATE { PROCEDURE | FOUNCTION } sp_name;
查看存储过程 proc_adder 的定义:
mysql> SHOW PROCEDURE STATUS LIKE 'proc_adder'\G;
*************************** 1. row ***************************
Db: daicooper
Name: proc_adder
Type: PROCEDURE
Definer: [email protected]
Modified: 2018-12-06 14:07:52
Created: 2018-12-06 14:07:52
Security_type: DEFINER
Comment:
character_set_client: utf8mb4
collation_connection: utf8mb4_unicode_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)
ERROR:
No query specified
mysql>
查看存储过程 proc_adder 的定义:
mysql> select * from information_schema.routines where ROUTINE_NAME = 'proc_adder'\G;
*************************** 1. row ***************************
SPECIFIC_NAME: proc_adder
ROUTINE_CATALOG: def
ROUTINE_SCHEMA: daicooper
ROUTINE_NAME: proc_adder
ROUTINE_TYPE: PROCEDURE
DATA_TYPE:
CHARACTER_MAXIMUM_LENGTH: NULL
CHARACTER_OCTET_LENGTH: NULL
NUMERIC_PRECISION: NULL
NUMERIC_SCALE: NULL
DATETIME_PRECISION: NULL
CHARACTER_SET_NAME: NULL
COLLATION_NAME: NULL
DTD_IDENTIFIER: NULL
ROUTINE_BODY: SQL
ROUTINE_DEFINITION: BEGIN
#Routine body goes here...
DECLARE c int;
if a is null then set a = 0;
end if;
if b is null then set b = 0;
end if;
set sum = a + b;
END
EXTERNAL_NAME: NULL
EXTERNAL_LANGUAGE: NULL
PARAMETER_STYLE: SQL
IS_DETERMINISTIC: NO
SQL_DATA_ACCESS: CONTAINS SQL
SQL_PATH: NULL
SECURITY_TYPE: DEFINER
CREATED: 2018-12-06 14:07:52
LAST_ALTERED: 2018-12-06 14:07:52
SQL_MODE: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
ROUTINE_COMMENT:
DEFINER: [email protected]
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_unicode_ci
DATABASE_COLLATION: utf8_general_ci
1 row in set (0.00 sec)
ERROR:
No query specified
mysql>
存储过程和函数中可以使用变量,变量是不区分大小写的。
1:变量的定义
通过 DECLARE 可以定义一个局部变量,改变量的作用范文只能在 BEGIN....END 块中,可以用在嵌套的块中。
变量的定义必须写在复合语句的开头,并且在任何其它语句的前面,可以一次声明多个相同类型的变量。如果需要,可以使用 DEFAULT 赋默认值。
定义一个变量的语法如下:
DELARE var_name [,....] type [DEFAULT value]
例如:定义一个 DATE 类型的变量 ,名称是 last_month_start
DELARE last_month_start DATE;
1:变量的赋值
变量可以直接赋值,或者通过查询赋值。直接赋值使用 SET ,可以赋值常量或者表达式,语法如下:
set last_month_start = DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH);
也可以通过查询将结果赋值给变量, 这要求查询返回的结果必须只有一行。具体语法如下:
select col_name [,....] INTO var_name [,....] table_expr
通过查询结果赋值给 last_month_start :
select DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH) INTO last_month_start from DUAL
可以使用 IF, CASE, LOOP, LEAVE, ITERATE, REPEAT,WHILE 语句进行流程控制。
1:IF 语句
if 实现条件判断,满足不同的条件执行不同的语句列表,具体语法:
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ....
[ELSE statement_list]
END IF
2:CASE 语句
CASE case_value
WHEN when_value THEN statement_list
[ WHEN when_value THEN statement_list ]...
[ ELSE statement_list]
END CASE
或者
CASE
WHEN search_condition THEN statement_list
[ WHEN search_condition THEN statement_list ]...
[ ELSE statement_list]
END CASE
比如 ,过程 proc_case 判断输入的参数值是 0 还是 1 或是 other:
CREATE PROCEDURE `proc_case`(IN type int)
BEGIN
#Routine body goes here...
DECLARE c varchar(500);
CASE type
WHEN 0 THEN
set c = 'param is 0';
WHEN 1 THEN
set c = 'param is 1';
ELSE
set c = 'param is others, not 0 or 1';
END CASE;
select c;
END
3:LOOP 语句
LOOP 语句实现简单的循环,退出循环的条件需要使用其它的语句定义,通常可以使用 LEAVE 语句实现,具体语法:
[begin_label:] LOOP
statement_list
END LOOP [end_label]
如果不在 statement_list 中增加退出循环的语句,那么 LOOP 语句可以用来实现简单的死循环。
4:LEAVE 语句
用来从标注的流程构造中退出,通常和 BEGIN...END 或循环一起使用。
下面是一个 LOOP 和 LEAVE 一起使用的一个简单的例子,循环一百次向 test 表中插入记录,当插入 100 条记录后退出循环。
CRATE PROCEDURE proc_Loop(OUT sum INT)
BEGIN
set @x = 0;
ins:LOOP
set @x = @x +1;
IF @x = 100 THEN
LEAVE ins;
END IF;
set sum = sum + @x;
END LOOP;
select sum;
END
执行结果
mysql> call proc_Loop(@sum);
+------+
| sum |
+------+
| 4950 |
+------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)