存储例程

陌路散爱 提交于 2020-03-21 03:25:52

存储例程是存储程序的一种类型,本质上也是封装了一些可执行的语句,只不过它的调用方式是:需要手动去调用!存储例程又可以分为存储函数和存储过程。

存储函数

创建存储函数

存储函数其实就是一种函数,只不过在这个函数里可以执行命令语句而已。函数它可以把处理某个问题的过程封装起来,之后直接调用函数就可以去解决同样的问题了。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 而且不需要参数的函数,在函数体中声明了一个名称为 cINT 类型变量,之后调用 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;

这个语句的意思是:如果满足给定的表达式,则执行循环语句,否则退出循环。比如想定义一个从 1nn 个数的和(假设 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 类型的变量 iresult 。先测试一下这个函数:

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,它的参数类型是 OUTp_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 参数类型

    知道了 INOUT 参数类型的意思,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 ,而存储过程支持 INOUTINOUT三种参数类型。
  • 存储函数只能返回一个值,而存储过程可以通过设置多个 OUT 类型的参数来返回多个结果。
  • 存储函数执行过程中产生的结果集并不会被显示到客户端,而存储过程执行过程中产生的结果集会被显示到客户端。
  • 存储函数的调用直接使用在表达式中,而存储过程只能通过 CALL 语句来显式调用。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!