存储例程
存储例程是存储程序的一种类型,本质上也是封装了一些可执行的语句,只不过它的调用方式是:需要手动去调用!存储例程又可以分为存储函数和存储过程。
存储函数
创建存储函数
存储函数其实就是一种函数,只不过在这个函数里可以执行命令语句而已。函数它可以把处理某个问题的过程封装起来,之后直接调用函数就可以去解决同样的问题了。MySQL 中定义存储函数的语句如下:
CREATE FUNCTION 存储函数名称([参数列表])
RETURNS 返回值类型
BEGIN
函数体内容
END
从这里可以看出,定义一个存储函数需要指定函数名称、参数列表、返回值类型以及函数体内容,如果该函数不需要参数,那参数列表可以被省略,函数体内容可以包括一条或多条语句,每条语句都要以分号 ;
结尾。里边的制表符和换行仅仅是为了好看,完全可以用空格代替。
mysql> delimiter $
mysql> create function avg_score(s varchar(100))
-> returns double
-> begin
-> return (select avg(score) from student_score where subject = s);
-> end $
Query OK, 0 rows affected (0.01 sec)
定义了一个名叫 avg_score
的函数,它接收一个 varchar(100)
类型的参数,声明的返回值类型是 double
,需要注意的是,在 return
语句后边写了一个 select
语句,表明这个函数的最后返回结果就是根据这个查询语句产生的,也就是返回了指定科目的平均成绩。
存储函数的调用
自定义的函数和系统内置函数的使用方式是一样的,都是在函数名后加小括号 ()
表示函数调用,有参数的函数调用可以把参数写到小括号里边。函数调用可以作为查询对象或者搜索条件,或者和别的操作数一起组成更复杂的表达式
mysql> select avg_score('母猪的产后护理');
+------------------------------------+
| avg_score('母猪的产后护理') |
+------------------------------------+
| 73 |
+------------------------------------+
1 row in set (0.01 sec)
mysql> select avg_score('论萨达姆的战争准备');
+------------------------------------------+
| avg_score('论萨达姆的战争准备') |
+------------------------------------------+
| 73.25 |
+------------------------------------------+
1 row in set (0.00 sec)
这样调用函数就比直接写两个查询语句简单多了
查看和删除存储函数
如果想查看已经定义了多少个存储函数,可以使用下边这个语句:
SHOW FUNCTION STATUS [LIKE 需要匹配的函数名]
比如这样:
mysql> show create function avg_score G
*************************** 1. row ***************************
Function: avg_score
sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
Create Function: CREATE DEFINER=`root`@`localhost` FUNCTION `avg_score`(s varchar(100)) RETURNS double
begin
return (select avg(score) from student_score where subject = s);
end
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)
如果想删除某个存储函数,使用这个语句:
DROP FUNCTION 函数名
比如删掉 avg_score
这个函数:
mysql> DROP FUNCTION avg_score;
Query OK, 0 rows affected (0.00 sec)
在函数体中定义变量
在函数体中使用变量前必须先声明这个变量,声明方式如下:
DECLARE 变量名 数据类型 [DEFAULT 默认值];
需要特别留心的是,函数体中的变量名不允许加 @
前缀,这一点和定义变量的方式是截然不同的,特别注意一下。在声明了这个变量之后,才可以使用它:
mysql> delimiter $;
mysql> CREATE FUNCTION var_demo()
-> RETURNS INT
-> BEGIN
-> DECLARE c INT;
-> SET c = 5;
-> RETURN c;
-> END $
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
这里定义了一个名叫 var_demo
而且不需要参数的函数,在函数体中声明了一个名称为 c
的 INT
类型变量,之后调用 SET
语句为这个变量赋值了整数 5
,并且把变量 c
当作函数结果返回,调用一下这个函数:
mysql> SELECT var_demo();
+------------+
| var_demo() |
+------------+
| 5 |
+------------+
1 row in set (0.00 sec)
如果不对声明的变量赋值的话,它的默认值就是 NULL
,当然也可以通过 DEFAULT
子句来显式的指定变量的默认值,比如这样:
mysql> delimiter $
mysql> CREATE FUNCTION var_default_demo()
-> RETURNS INT
-> BEGIN
-> DECLARE c INT DEFAULT 1;
-> RETURN c;
-> END $
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
在新创建的这个 var_default_demo
函数中,声明了一个变量 c
,并且指定了它的默认值为 1
,然后看一下函数的调用结果:
mysql> SELECT var_default_demo();
+--------------------+
| var_default_demo() |
+--------------------+
| 1 |
+--------------------+
1 row in set (0.00 sec)
得到的结果是 1
,说明了指定的变量默认值生效了。另外,特别需要注意一下,可以将某个查询语句的结果赋值给变量的情况,比如改写一下前边的 avg_score
函数:
CREATE FUNCTION avg_score(s VARCHAR(100))
RETURNS DOUBLE
BEGIN
DECLARE a DOUBLE;
SET a = (SELECT AVG(score) FROM student_score WHERE subject = s);
return a;
END
先把一个查询语句的结果赋值给了变量 a
,然后再返回了这个变量。
参数的编写
在定义函数的时候,可以指定多个参数,每个参数都要指定对应的数据类型,就像这样:
参数名 数据类型
比如上边编写的这个 avg_score
函数:
CREATE FUNCTION avg_score(s VARCHAR(100))
RETURNS DOUBLE
BEGIN
RETURN (SELECT AVG(score) FROM student_score WHERE subject = s);
END
这个函数只需要一个类型为 VARCHAR(100)
参数,这里给这个参数起的名称是 s
,需要注意的是,参数名不要和函数体语句中其他的变量名、命令语句的标识符冲突,如果把这个变量名命名为 subject
,它就与下边用到 WHERE
子句中的列名冲突了,导致列名失效。
另外,函数参数不可以指定默认值,在调用函数的时候,必须显式的指定所有的参数,并且参数类型也一定要匹配,比方说在调用函数 avg_score
时,必须指定要查询的课程名,不然会报错的:
mysql> select avg_score();
ERROR 1318 (42000): Incorrect number of arguments for FUNCTION test.avg_score; expected 1, got 0
判断语句的编写
像其他的编程语言一样,在 MySQL 的函数体里也可以使用判断的语句,语法格式如下:
IF 布尔表达式 THEN
处理语句
[ELSEIF 布尔表达式 THEN
处理语句]
[ELSE
处理语句]
END IF;
需要注意的是,这里的处理语句可以是由多条语句构成的复合语句。举个例子:
mysql> delimiter $
mysql> CREATE FUNCTION condition_demo(i INT)
-> RETURNS VARCHAR(10)
-> BEGIN
-> DECLARE result VARCHAR(10);
-> IF i = 1 THEN
-> SET result = '结果是1';
-> ELSEIF i = 2 THEN
-> SET result = '结果是2';
-> ELSEIF i = 3 THEN
-> SET result = '结果是3';
-> ELSE
-> SET result = '非法参数';
-> END IF;
-> RETURN result;
-> END $
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
现在调用一下这个函数:
mysql> SELECT condition_demo(2);
+-------------------+
| condition_demo(2) |
+-------------------+
| 结果是2 |
+-------------------+
1 row in set (0.00 sec)
mysql> SELECT condition_demo(5);
+-------------------+
| condition_demo(5) |
+-------------------+
| 非法参数 |
+-------------------+
1 row in set (0.00 sec)
循环语句的编写
除了判断语句,MySQL 还支持循环语句的编写,不过有 3 种形式的循环语句:
WHILE 循环语句
WHILE 布尔表达式 DO
循环语句
END WHILE;
这个语句的意思是:如果满足给定的表达式,则执行循环语句,否则退出循环。比如想定义一个从 1
到 n
这 n
个数的和(假设 n
大于 0
),可以这么写:
mysql> delimiter $
mysql> CREATE FUNCTION sum_all(n INT UNSIGNED)
-> RETURNS INT
-> BEGIN
-> DECLARE result INT DEFAULT 0;
-> DECLARE i INT DEFAULT 1;
-> WHILE i <= n DO
-> SET result = result + i;
-> SET i = i + 1;
-> END WHILE;
-> RETURN result;
-> END $
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
在函数 sum_all
中,接收一个 INT UNSIGNED
类型的参数,声明了两个 INT
类型的变量 i
和 result
。先测试一下这个函数:
mysql> SELECT sum_all(3);
+------------+
| sum_all(3) |
+------------+
| 6 |
+------------+
1 row in set (0.00 sec)
REPEAT 循环语句
REPEAT 循环和 WHILE 循环差不多,只是形式上变了一下:
REPEAT
循环语句
UNTIL 布尔表达式 END REPEAT;
先执行循环语句,再判断布尔表达式是否成立,如果成立继续执行循环语句,否则退出循环。与 WHILE 循环不同的一点是:WHILE 循环先判断布尔表达式的值,再执行循环语句,REPEAT 循环先执行循环语句,再判断布尔表达式的值,所以至少执行一次循环语句,所以如果 sum_all
函数用 REPEAT 循环改写,可以写成这样:
CREATE FUNCTION sum_all(n INT UNSIGNED)
RETURNS INT
BEGIN
DECLARE result INT DEFAULT 0;
DECLARE i INT DEFAULT 1;
REPEAT
SET result = result + i;
SET i = i + 1;
UNTIL i <= n END REPEAT;
RETURN result;
END
LOOP 循环语句
这只是另一种形式的循环语句:
循环标记:LOOP
循环语句
LEAVE 循环标记;
END LOOP 循环标记;
在 LOOP 循环语句中,比较特别的是需要设置 循环标记 来标识一个循环,在循环体内依靠 LEAVE 循环标记的形式来中断某个循环,比方说可以把 sum_all
函数改写成这样:
CREATE FUNCTION sum_all(n INT UNSIGNED)
RETURNS INT
BEGIN
DECLARE result INT DEFAULT 0;
DECLARE i INT DEFAULT 1;
flag:LOOP
IF i > n THEN
LEAVE flag;
END IF;
SET result = result + i;
SET i = i + 1;
END LOOP flag;
RETURN result;
END
其中的 flag
就是一个 循环标记
,在循环体内判断 i > n
成立的时候就调用 LEAVE flag
来跳出这个循环。
注释的使用
不论什么时候,对语句添加注释都是一件好事儿!注释不仅仅是帮助别人理解写的语句是什么意思,对于自己来说,可能隔了几天之后再看自己写的语句就不知道是什么意思了。在函数体内以 --
开头的语句都算作注释语句,MySQL 服务器在执行语句的时候会忽略掉这些注释语句。
-- 函数名:sum_all
-- 参数:n = 从1累加到的数字
CREATE FUNCTION sum_all(n INT UNSIGNED) COMMENT '求1到n这n个数的和'
RETURNS INT
BEGIN
-- 当前累加的和
DECLARE result INT DEFAULT 0;
-- 当前累加的数字
DECLARE i INT DEFAULT 1;
-- 若当前累加的数字不大于指定数字,则继续执行循环
WHILE i <= n DO
SET result = result + i;
SET i = i + 1;
END WHILE;
-- 返回累加的和
RETURN result;
END
除了 --
开头的语句表示注释,我们还可以在函数参数后写 COMMENT
注释语句说明这个函数的作用。
存储过程
创建存储过程
存储函数和存储过程都属于存储例程,都是对某些语句的一个封装。存储函数侧重于执行这些语句并返回一个值,而存储过程更侧重于单纯的去执行这些语句。先看一下存储过程的定义语句:
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
需要执行的语句
END
与存储函数最直观的不同点就是,存储过程的定义不需要声明返回值类型。为了更直观的理解,先定义一个存储过程看看:
mysql> delimiter $
mysql> CREATE PROCEDURE t1_operation(
-> m1_value INT,
-> n1_value CHAR(1)
-> )
-> BEGIN
-> SELECT * FROM t1;
-> INSERT INTO t1(m1, n1) VALUES(m1_value, n1_value);
-> SELECT * FROM t1;
-> END $
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
这里建立了一个名叫 t1_operation
的存储过程,它接收两个参数,一个是 INT
类型的,一个是 CHAR(1)
类型的。这个存储过程做了 3 件事儿,一件是查询一下 t1
表中的数据,第二件是根据接收的参数来向 t1
表中插入一条语句,第三件是再次查询一下 t1
表中的数据。
存储过程的调用
存储函数执行语句并返回一个值,所以常用在表达式中。存储过程偏向于调用那些语句,并不能用在表达式中,需要显式的使用 CALL
语句来调用一个存储过程:
CALL 存储过程([参数列表]);
比方说调用一下 t1_operation
存储过程可以这么写:
mysql> CALL t1_operation(4, 'd');
+------+------+
| m1 | n1 |
+------+------+
| 1 | a |
| 2 | b |
| 3 | c |
+------+------+
3 rows in set (0.00 sec)
+------+------+
| m1 | n1 |
+------+------+
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
+------+------+
4 rows in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
从执行结果中可以看到,存储过程在执行中产生的所有结果集,全部将会被显示到客户端。
只有查询语句才会产生结果集,更新语句是不产生结果集的,所以那条
INSERT
语句所产生的输出没有被显示出来。
查看和删除存储过程
与存储函数类似,存储过程也有相似的查看和删除语句,下边只列举一下相关语句,就不举例子了。
查看当前数据库中创建的存储过程都有哪些的语句:
SHOW PROCEDURE STATUS [LIKE 需要匹配的函数名]
查看某个存储过程定义的语句:
SHOW CREATE PROCEDURE 存储过程名称
删除存储过程的语句:
DROP PROCEDURE 存储过程名称
存储过程中的语句
上边介绍了存储函数中使用到的各种语句,包括变量的使用、判断、循环结构、注释的使用都可以被用在存储过程中,这里就不再赘述了。
存储过程的参数类型
相比存储函数,存储过程在定义参数的时候可以选择参数类型(注意!不是数据类型),就像是这个样子:
参数类型 参数名 数据类型
这个所谓的参数类型有 3 种:
-
IN
参数类型先定义一个类型参数是
IN
的存储过程p_in
:mysql> delimiter $ mysql> create procedure p_in ( -> in arg int -> ) -> begin -> -- 语句一:读取参数 -> select arg; -> -- 语句二:为参数赋值 -> set arg = 123; -> end $ Query OK, 0 rows affected (0.00 sec) mysql> delimiter ;
这个
p_in
存储过程只有一个参数arg
,它的参数类型是IN
,这个存储过程实际执行两个语句,第一个语句是用来读取参数的值,第二个语句是给参数赋值。调用一下p_in
:mysql> SET @a = 1; Query OK, 0 rows affected (0.00 sec) mysql> CALL p_in(@a); +------+ | arg | +------+ | 1 | +------+ 1 row in set (0.00 sec) Query OK, 0 rows affected (0.00 sec) mysql> SELECT @a; +------+ | @a | +------+ | 1 | +------+ 1 row in set (0.00 sec)
这里在客户端定义了一个变量
a
并赋值1
,因为它是在客户端定义的,所以需要加@
前缀,然后把它当作参数传给p_in
存储过程。从结果中可以看出,第一个读取语句被成功执行,虽然第二个语句没有报错,但是再存储过程执行完毕后,再次查看变量a
的值并没有改变,这也就是说:IN
参数类型的变量只能用于读取,对类型的变量赋值是不会被调用者看到的。另外,因为对于参数类型是
IN
的参数,我们只是想在存储函数执行中使用它,并不需要把执行结果存储到它里边,所以除了让变量作为函数参数,常量也是可以的,比如这样:mysql> CALL p_in(1); +------+ | arg | +------+ | 1 | +------+ 1 row in set (0.00 sec) Query OK, 0 rows affected (0.00 sec)
-
OUT
参数类型先定义一个类型参数是
OUT
的存储过程p_out
:mysql> delimiter $ mysql> CREATE PROCEDURE p_out ( -> OUT a INT -> ) -> BEGIN -> SELECT a; -> SET a = 123; -> END $ Query OK, 0 rows affected (0.00 sec) mysql> delimiter ;
这个
p_out
存储过程只有一个参数arg
,它的参数类型是OUT
,p_out
存储过程也有两个语句,一个用于读取参数的值,另一个用于为参数赋值,调用一下p_out
:mysql> SET @b = 2; Query OK, 0 rows affected (0.00 sec) mysql> CALL p_out(@b); +------+ | a | +------+ | NULL | +------+ 1 row in set (0.00 sec) Query OK, 0 rows affected (0.00 sec) mysql> SELECT @b; +------+ | @b | +------+ | 123 | +------+ 1 row in set (0.00 sec)
在客户端定义了一个变量
b
并赋值2
,然后把它当作参数传给p_out
存储过程。从结果中可以看出,第一个读取语句并没有获取到参数的值,在存储过程执行完毕之后,再次读取变量b
的值,发现它的值已经被设置成123
,说明在过程中对该变量的赋值对调用者是可见的!这也就是说:OUT
参数类型的变量只能用于赋值,对类型的变量赋值是会被调用者看到的。另外,由于
OUT
参数类型的参数只是为了用于在过程中赋值后被调用者查看,那实际的参数就不允许是常量,常量还怎么赋值啊! -
INOUT
参数类型知道了
IN
和OUT
参数类型的意思,INOUT
也就明白了,这种类型的参数既可以在存储过程中被读取,也可以被赋值后被调用者看到,所以要求实际的参数必须是一个变量,不然无法赋值!INOUT
参数类型就不具体举例子了
需要注意的是,如果不写明参数类型的话,该参数的类型默认是 IN
,之前一直没有注明参数类型,所以之前使用的参数类型都是 IN
!
由于可以传入多个 OUT
或者 INOUT
类型的参数,所以可以在一个存储过程中获得多个结果,比如这样:
mysql> delimiter $
mysql> CREATE PROCEDURE get_score_data(
-> OUT max_score DOUBLE,
-> OUT min_score DOUBLE,
-> OUT avg_score DOUBLE,
-> s VARCHAR(100)
-> )
-> BEGIN
-> SELECT MAX(score), MIN(score), AVG(score) FROM student_score WHERE subject = s INTO max_score, min_score, avg_score;
-> END $
Query OK, 0 rows affected (0.02 sec)
mysql> delimiter ;
定义的这个 get_score_data
存储过程接受 4 个参数,前三个参数都是 OUT
类型的参数,第四个参数没写参数类型,默认就是 IN
类型。存储过程的内容是将指定学科的最高分、最低分、平均分赋值给三个 OUT
类型的参数。在这个存储过程执行完之后,我们可以根据通过访问这几个 OUT
类型的参数来获得相应的最高分、最低分以及平均分:
mysql> CALL get_score_data(@a, @b, @c, '母猪的产后护理');
Query OK, 1 row affected (0.01 sec)
mysql> SELECT @a, @b, @c;
+------+------+------+
| @a | @b | @c |
+------+------+------+
| 100 | 55 | 73 |
+------+------+------+
1 row in set (0.00 sec)
这个例子说明了:可以在存储过程中向调用者返回多个值,而存储函数只能返回一个值。
存储过程和存储函数的不同点
存储过程和存储函数都是某些语句的一个封装,而且使用的语法格式都是一样的,下面着重说一下它们的不同点:
- 存储函数在定义时需要显式用
RETURNS
语句标明返回的数据类型,而且在函数体中必须使用RETURN
语句来显式指定返回的值,存储过程不需要。 - 存储函数的参数类型只能是
IN
,而存储过程支持IN
、OUT
、INOUT
三种参数类型。 - 存储函数只能返回一个值,而存储过程可以通过设置多个
OUT
类型的参数来返回多个结果。 - 存储函数执行过程中产生的结果集并不会被显示到客户端,而存储过程执行过程中产生的结果集会被显示到客户端。
- 存储函数的调用直接使用在表达式中,而存储过程只能通过
CALL
语句来显式调用。