第17章、存储函数和存储过程

标签: MySQL是怎样使用的新版


我们前边说可以将某个常用功能对应的的一些语句封装成一个所谓的存储程序,之后只要调用这个存储程序就可以完成这个常用功能,省去了我们每次都要写好多语句的麻烦。存储程序可以被分为存储例程触发器事件这几种类型,其中存储例程需要我们去手动调用,而触发器事件都是MySQL服务器在特定条件下自己调用的。本章我们就来看一下存储例程的各种细节,而存储例程又可以分为存储函数存储过程,下边我们详细唠叨这两个家伙。

小贴士: 各位小伙伴有没有被绕晕,我们前边画过一副这些名词儿的关系图,有被绕晕的话赶紧去看看治疗一下呗~

存储函数

创建存储函数

存储函数其实就是一种函数,只不过在这个函数里可以执行MySQL的语句而已。函数的概念大家都应该不陌生,它可以把处理某个问题的过程封装起来,之后我们直接调用函数就可以去解决这个问题了,简单方便又环保。MySQL中定义存储函数的语句如下:

  1. CREATE FUNCTION 存储函数名称([参数列表])
  2. RETURNS 返回值类型
  3. BEGIN
  4. 函数体内容
  5. END

从这里我们可以看出,定义一个存储函数需要指定函数名称、参数列表、返回值类型以及函数体内容。如果该函数不需要参数,那参数列表可以被省略,函数体内容可以包括一条或多条语句,每条语句都要以分号;结尾。上边语句中的制表符和换行仅仅是为了好看,如果你觉得烦,完全可以把存储函数的定义都写在一行里,用一个或多个空格把上述几个部分分隔开就好! 光看定义理解的不深刻,我们先写一个存储函数开开眼:

  1. mysql> delimiter $
  2. mysql> CREATE FUNCTION avg_score(s VARCHAR(100))
  3. -> RETURNS DOUBLE
  4. -> BEGIN
  5. -> RETURN (SELECT AVG(score) FROM student_score WHERE subject = s);
  6. -> END $
  7. Query OK, 0 rows affected (0.00 sec)
  8. mysql> delimiter ;

我们定义了一个名叫avg_score的函数,它接收一个VARCHAR(100)类型的参数,声明的返回值类型是DOUBLE,需要注意的是,我们在RETURN语句后边写了一个SELECT语句,表明这个函数的返回结果就是根据这个查询语句产生的,也就是返回了指定科目的平均成绩。

存储函数的调用

我们自定义的函数和系统内置函数的使用方式是一样的,都是在函数名后加小括号()表示函数调用,调用有参数的函数时可以把参数写到小括号里边。函数调用可以放到查询列表或者作为搜索条件,或者和别的操作数一起组成更复杂的表达式,我们现在来调用一下刚刚写好的这个名为avg_score的函数吧:

  1. mysql> SELECT avg_score('母猪的产后护理');
  2. +------------------------------------+
  3. | avg_score('母猪的产后护理') |
  4. +------------------------------------+
  5. | 73 |
  6. +------------------------------------+
  7. 1 row in set (0.00 sec)
  8. mysql> SELECT avg_score('论萨达姆的战争准备');
  9. +------------------------------------------+
  10. | avg_score('论萨达姆的战争准备') |
  11. +------------------------------------------+
  12. | 73.25 |
  13. +------------------------------------------+
  14. 1 row in set (0.00 sec)
  15. mysql>

通过调用函数的方式而不是直接写查询语句的方式来获取某门科目的平均成绩看起来就简介多了。

查看和删除存储函数

如果我们想查看我们已经定义了多少个存储函数,可以使用下边这个语句:

  1. SHOW FUNCTION STATUS [LIKE 需要匹配的函数名]

由于这个命令得到的结果太多,我们就不演示了哈,大家可以自己试试。如果我们想查看某个函数的具体是怎么定义的,可以使用这个语句:

  1. SHOW CREATE FUNCTION 函数名

比如这样:

  1. mysql> SHOW CREATE FUNCTION avg_score\G
  2. *************************** 1. row ***************************
  3. Function: avg_score
  4. sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
  5. Create Function: CREATE DEFINER=`root`@`localhost` FUNCTION `avg_score`(s VARCHAR(100)) RETURNS double
  6. BEGIN
  7. RETURN (SELECT AVG(score) FROM student_score WHERE subject = s);
  8. END
  9. character_set_client: utf8
  10. collation_connection: utf8_general_ci
  11. Database Collation: utf8_general_ci
  12. 1 row in set (0.01 sec)
  13. mysql>

虽然展示出很多内容,但是我们只要聚焦于名叫Create Function的那部分信息,该部分信息展示了这个存储函数的定义语句是什么样的(可以看到MySQL服务器为我们自动添加了DEFINER=`root`@`localhost` ,大家可以把这个内容先忽略掉)。

如果想删除某个存储函数,使用这个语句:

  1. DROP FUNCTION 函数名

比如我们来删掉avg_score这个函数:

  1. mysql> DROP FUNCTION avg_score;
  2. Query OK, 0 rows affected (0.00 sec)
  3. mysql>

什么?你以为到这里存储函数就唠叨完了么?那怎么可能~ 到现在为止我们只是勾勒出一个存储函数的大致轮廓,下边我们来详细说一下MySQL定义函数体时支持的一些语句。

函数体的定义

上边定义的avg_score的函数体里边只包含一条语句,如果只为了节省书写一条语句的时间而定义一个存储函数,其实也不是很值~ 其实存储函数的函数体中可以包含多条语句,并且支持一些特殊的语法来供我们使用,下边一起看看呗~

在函数体中定义局部变量

我们在前边说过使用SET语句来自定义变量的方式,可以不用声明就为变量赋值。而在存储函数的函数体中使用变量前必须先声明这个变量,声明方式如下:

  1. DECLARE 变量名1, 变量名2, ... 数据类型 [DEFAULT 默认值];

这些在函数体内声明的变量只在该函数体内有用,当存储函数执行完成后,就不能访问到这些变量了,所以这些变量也被称为局部变量。我们可以在一条语句中声明多个相同数据类型的变量。不过需要特别留心的是,函数体中的局部变量名不允许加@前缀,这一点和我们之前直接使用SET语句自定义变量的方式是截然不同的,特别注意一下。在声明了这个局部变量之后,才可以使用它,就像这样:

  1. mysql> delimiter $;
  2. mysql> CREATE FUNCTION var_demo()
  3. -> RETURNS INT
  4. -> BEGIN
  5. -> DECLARE c INT;
  6. -> SET c = 5;
  7. -> RETURN c;
  8. -> END $
  9. Query OK, 0 rows affected (0.00 sec)
  10. mysql> delimiter ;

我们定义了一个名叫var_demo而且不需要参数的函数,然后在函数体中声明了一个名称为cINT类型的局部变量,之后我们调用SET语句为这个局部变量赋值了整数5,并且把局部变量c当作函数结果返回。我们调用一下这个函数:

  1. mysql> select var_demo();
  2. +------------+
  3. | var_demo() |
  4. +------------+
  5. | 5 |
  6. +------------+
  7. 1 row in set (0.00 sec)
  8. mysql>

如果我们不对声明的局部变量赋值的话,它的默认值就是NULL,当然我们也可以通过DEFAULT子句来显式的指定局部变量的默认值,比如这样:

  1. mysql> delimiter $
  2. mysql> CREATE FUNCTION var_default_demo()
  3. -> RETURNS INT
  4. -> BEGIN
  5. -> DECLARE c INT DEFAULT 1;
  6. -> RETURN c;
  7. -> END $
  8. Query OK, 0 rows affected (0.00 sec)
  9. mysql> delimiter ;
  10. mysql>

在新创建的这个var_default_demo函数中,我们声明了一个局部变量c,并且指定了它的默认值为1,然后看一下该函数的调用结果:

  1. mysql> SELECT var_default_demo();
  2. +--------------------+
  3. | var_default_demo() |
  4. +--------------------+
  5. | 1 |
  6. +--------------------+
  7. 1 row in set (0.00 sec)
  8. mysql>

得到的结果是1,说明了我们指定的局部变量默认值生效了!另外,特别需要注意一下我们可以将某个查询语句的结果赋值给局部变量的情况,比如我们改写一下前边的avg_score函数:

  1. CREATE FUNCTION avg_score(s VARCHAR(100))
  2. RETURNS DOUBLE
  3. BEGIN
  4. DECLARE a DOUBLE;
  5. SET a = (SELECT AVG(score) FROM student_score WHERE subject = s);
  6. return a;
  7. END

我们先把一个查询语句的结果赋值给了局部变量a,然后再返回了这个变量。

小贴士: 在存储函数的函数体中,DECLARE语句必须放到其他语句的前边。

在函数体中使用自定义变量

除了局部变量外,也可以在函数体中使用我们之前用过的自定义变量,比方说这样:

  1. mysql> delimiter $
  2. mysql>
  3. mysql> CREATE FUNCTION user_defined_var_demo()
  4. -> RETURNS INT
  5. -> BEGIN
  6. -> SET @abc = 10;
  7. -> return @abc;
  8. -> END $
  9. Query OK, 0 rows affected (0.00 sec)
  10. mysql>
  11. mysql> delimiter ;
  12. mysql>

我们定义了一个名叫user_defined_var_demo的存储函数,函数体内直接使用了自定义变量abc,我们来调用一下这个函数:

  1. mysql> SELECT user_defined_var_demo();
  2. +-------------------------+
  3. | user_defined_var_demo() |
  4. +-------------------------+
  5. | 10 |
  6. +-------------------------+
  7. 1 row in set (0.01 sec)
  8. mysql>

虽然现在存储函数执行完了,但是由于在该函数执行过程中为自定义变量abc赋值了,那么在该函数执行完之后我们仍然可以访问到该自定义变量的值,就像这样:

  1. mysql> SELECT @abc;
  2. +------+
  3. | @abc |
  4. +------+
  5. | 10 |
  6. +------+
  7. 1 row in set (0.00 sec)
  8. mysql>

这一点和在函数体中使用DECLARE声明的局部变量有明显区别,大家注意一下。

存储函数的参数

在定义存储函数的时候,可以指定多个参数,每个参数都要指定对应的数据类型,就像这样:

  1. 参数名 数据类型

比如我们上边编写的这个avg_score函数:

  1. CREATE FUNCTION avg_score(s VARCHAR(100))
  2. RETURNS DOUBLE
  3. BEGIN
  4. RETURN (SELECT AVG(score) FROM student_score WHERE subject = s);
  5. END

这个函数只需要一个类型为VARCHAR(100)参数,我们这里给这个参数起的名称是s,不过这个参数名不要和函数体语句中的其他变量名、列名啥的冲突,比如上边的例子中如果把变量名s改为为subject,它就与下边用到WHERE子句中的列名冲突了。

另外,函数参数不可以指定默认值,我们在调用函数的时候,必须显式的指定所有的参数,并且参数类型也一定要匹配,比方说我们在调用函数avg_score时,必须指定我们要查询的课程名,不然会报错的:

  1. mysql> select avg_score();
  2. ERROR 1318 (42000): Incorrect number of arguments for FUNCTION xiaohaizi.avg_score; expected 1, got 0
  3. mysql>

判断语句的编写

像其他的编程语言一样,在存储函数的函数体里也可以使用判断的语句,语法格式如下:

  1. IF 表达式 THEN
  2. 处理语句列表
  3. [ELSEIF 表达式 THEN
  4. 处理语句列表]
  5. ... # 这里可以有多个ELSEIF语句
  6. [ELSE
  7. 处理语句列表]
  8. END IF;

其中处理语句列表中可以包含多条语句,每条语句以分号;结尾就好。

我们举一个包含IF语句的存储函数的例子:

  1. mysql> delimiter $
  2. mysql> CREATE FUNCTION condition_demo(i INT)
  3. -> RETURNS VARCHAR(10)
  4. -> BEGIN
  5. -> DECLARE result VARCHAR(10);
  6. -> IF i = 1 THEN
  7. -> SET result = '结果是1';
  8. -> ELSEIF i = 2 THEN
  9. -> SET result = '结果是2';
  10. -> ELSEIF i = 3 THEN
  11. -> SET result = '结果是3';
  12. -> ELSE
  13. -> SET result = '非法参数';
  14. -> END IF;
  15. -> RETURN result;
  16. -> END $
  17. Query OK, 0 rows affected (0.00 sec)
  18. mysql> delimiter ;
  19. mysql>

在我们定义的函数condition_demo中,它接收一个INT类型的参数,这个函数的处理逻辑如下:

  1. 如果这个参数的值是1,就把result变量的值设置为'结果是1'
  2. 否则如果这个这个参数的值是2,就把result变量的值设置为'结果是2'
  3. 否则如果这个这个参数的值是3,就把result变量的值设置为'结果是3'
  4. 否则就把result变量的值设置为'非法参数'

当然了,我们举的这个例子还是比较白痴的啦,只是为了说明语法怎么用而已。我们现在调用一下这个函数:

  1. mysql> SELECT condition_demo(2);
  2. +-------------------+
  3. | condition_demo(2) |
  4. +-------------------+
  5. | 结果是2 |
  6. +-------------------+
  7. 1 row in set (0.00 sec)
  8. mysql> SELECT condition_demo(5);
  9. +-------------------+
  10. | condition_demo(5) |
  11. +-------------------+
  12. | 非法参数 |
  13. +-------------------+
  14. 1 row in set (0.00 sec)
  15. mysql>

循环语句的编写

除了判断语句,MySQL还支持循环语句的编写,不过提供了3种形式的循环语句,我们一一道来:

  • WHILE循环语句:

    1. WHILE 表达式 DO
    2. 处理语句列表
    3. END WHILE;
  1. 这个语句的意思是:如果满足给定的表达式,则执行处理语句,否则退出循环。比如我们想定义一个计算从`1``n``n`个数的和(假设`n`大于`0`)的存储函数,可以这么写:
  2. mysql> delimiter $
  3. mysql> CREATE FUNCTION sum_all(n INT UNSIGNED)
  4. -> RETURNS INT
  5. -> BEGIN
  6. -> DECLARE result INT DEFAULT 0;
  7. -> DECLARE i INT DEFAULT 1;
  8. -> WHILE i <= n DO
  9. -> SET result = result + i;
  10. -> SET i = i + 1;
  11. -> END WHILE;
  12. -> RETURN result;
  13. -> END $
  14. Query OK, 0 rows affected (0.00 sec)
  15. mysql> delimiter ;
  16. mysql>
  17. 在函数`sum_all`中,我们接收一个`INT UNSIGNED`类型的参数,声明了两个`INT`类型的变量`i``result`。我们先测试一下这个函数:
  18. mysql> SELECT sum_all(3);
  19. +------------+
  20. | sum_all(3) |
  21. +------------+
  22. | 6 |
  23. +------------+
  24. 1 row in set (0.00 sec)
  25. mysql>
  26. 分析一下这个结果是怎么产生的,初始的情况下`result`的值默认是`0``i`的值默认是`1`,给定的参数`n`的值是`3`。这个存储函数的运行过程就是:
  27. 1. 先判断`i <= n`是否成立,也就是`1 <= 3`是否成立,显然成立,然后执行处理语句,将`result`的值设置为`1``result + i` \= `0 + 1`),`i`的值设置为`2``i + 1` \= `1 + 1`)。
  28. 2. 再判断`i <= n`是否成立,也就是`2 <= 3`是否成立,显然成立,然后执行处理语句,将`result`的值设置为`3``result + i` \= `1 + 2`),`i`的值设置为`3``i + 1` \= `2 + 1`)。
  29. 3. 再判断`i <= n`是否成立,也就是`3 <= 3`是否成立,显然成立,然后执行处理语句,将`result`的值设置为`6``result + i` \= `3 + 3`),`i`的值设置为`4``i + 1` \= `3 + 1`)。
  30. 4. 再判断`i <= n`是否成立,也就是`4 <= 3`是否成立,显然不成立,退出循环。
  31. 所以最后返回的`result`的值就是`6`,也就是`1``2``3`这三个数的和。
  • REPEAT循环语句

    REPEAT循环语句和WHILE循环语句类似,只是形式上变了一下:

    1. REPEAT
    2. 处理语句列表
    3. UNTIL 表达式 END REPEAT;
  1. 先执行处理语句,再判断`表达式`是否成立,如果成立则退出循环,否则继续执行处理语句。与`WHILE`循环语句不同的一点是:WHILE循环语句先判断表达式的值,再执行处理语句,REPEAT循环语句先执行处理语句,再判断表达式的值,所以至少执行一次处理语句,所以如果`sum_all`函数用`REPEAT`循环改写,可以写成这样:
  2. CREATE FUNCTION sum_all(n INT UNSIGNED)
  3. RETURNS INT
  4. BEGIN
  5. DECLARE result INT DEFAULT 0;
  6. DECLARE i INT DEFAULT 1;
  7. REPEAT
  8. SET result = result + i;
  9. SET i = i + 1;
  10. UNTIL i > n END REPEAT;
  11. RETURN result;
  12. END
  • LOOP循环语句

    这只是另一种形式的循环语句:

    1. LOOP
    2. 处理语句列表
    3. END LOOP;
  1. 不过这种循环语句有一点比较奇特,它没有判断循环终止的条件?那这个循环语句怎么停止下来呢?其实可以把循环终止的条件写到处理语句列表中然后使用`RETURN`语句直接让函数结束就可以达到停止循环的效果,比方说我们可以这样改写`sum_all`函数:
  2. CREATE FUNCTION sum_all(n INT UNSIGNED)
  3. RETURNS INT
  4. BEGIN
  5. DECLARE result INT DEFAULT 0;
  6. DECLARE i INT DEFAULT 1;
  7. LOOP
  8. IF i > n THEN
  9. RETURN result;
  10. END IF;
  11. SET result = result + i;
  12. SET i = i + 1;
  13. END LOOP;
  14. END
  15. 如果我们仅仅想结束循环,而不是使用`RETURN`语句直接将函数返回,那么可以使用`LEAVE`语句。不过使用`LEAVE`时,需要先在`LOOP`语句前边放置一个所谓的`标记`,比方说我们使用`LEAVE`语句再改写`sum_all`函数:
  16. CREATE FUNCTION sum_all(n INT UNSIGNED)
  17. RETURNS INT
  18. BEGIN
  19. DECLARE result INT DEFAULT 0;
  20. DECLARE i INT DEFAULT 1;
  21. flag:LOOP
  22. IF i > n THEN
  23. LEAVE flag;
  24. END IF;
  25. SET result = result + i;
  26. SET i = i + 1;
  27. END LOOP flag;
  28. RETURN result;
  29. END
  30. 可以看到,我们在`LOOP`语句前加了一个`flag:`这样的东东,相当于为这个循环打了一个名叫`flag`的标记,然后在对应的`END LOOP`语句后边也把这个标记名`flag`给写上了。在存储函数的函数体中使用`LEAVE flag`语句来结束`flag`这个标记所代表的循环。
  31. > 小贴士: 其实也可以在BEGIN ... ENDREPEATWHILE这些语句上打标记,标记主要是为了在这些语句发生嵌套时可以跳到指定的语句中使用的。

存储过程

创建存储过程

存储函数存储过程都属于存储例程,都是对某些语句的一个封装。存储函数侧重于执行这些语句并返回一个值,而存储过程更侧重于单纯的去执行这些语句。先看一下存储过程的定义语句:

  1. CREATE PROCEDURE 存储过程名称([参数列表])
  2. BEGIN
  3. 需要执行的语句
  4. END

存储函数最直观的不同点就是,存储过程的定义不需要声明返回值类型。我们先定义一个存储过程看看:

  1. mysql> delimiter $
  2. mysql> CREATE PROCEDURE t1_operation(
  3. -> m1_value INT,
  4. -> n1_value CHAR(1)
  5. -> )
  6. -> BEGIN
  7. -> SELECT * FROM t1;
  8. -> INSERT INTO t1(m1, n1) VALUES(m1_value, n1_value);
  9. -> SELECT * FROM t1;
  10. -> END $
  11. Query OK, 0 rows affected (0.00 sec)
  12. mysql> delimiter ;
  13. mysql>

我们建立了一个名叫t1_operation的存储过程,它接收两个参数,一个是INT类型的,一个是CHAR(1)类型的。这个存储过程做了3件事儿,一件是查询一下t1表中的数据,第二件是根据接收的参数来向t1表中插入一条语句,第三件是再次查询一下t1表中的数据。

存储过程的调用

存储函数执行语句并返回一个值,所以常用在表达式中。存储过程偏向于执行某些语句,并不能用在表达式中,我们需要显式的使用CALL语句来调用一个存储过程

  1. CALL 存储过程([参数列表]);

比方说我们调用一下t1_operation存储过程可以这么写:

  1. mysql> CALL t1_operation(4, 'd');
  2. +------+------+
  3. | m1 | n1 |
  4. +------+------+
  5. | 1 | a |
  6. | 2 | b |
  7. | 3 | c |
  8. +------+------+
  9. 3 rows in set (0.00 sec)
  10. +------+------+
  11. | m1 | n1 |
  12. +------+------+
  13. | 1 | a |
  14. | 2 | b |
  15. | 3 | c |
  16. | 4 | d |
  17. +------+------+
  18. 4 rows in set (0.00 sec)
  19. Query OK, 0 rows affected (0.00 sec)
  20. mysql>

从执行结果中可以看到,存储过程在执行中产生的所有结果集,全部将会被显示到客户端。

小贴士: 只有查询语句才会产生结果集,其他语句是不产生结果集的。

查看和删除存储过程

存储函数类似,存储过程也有相似的查看和删除语句,我们下边只列举一下相关语句,就不举例子了。

查看当前数据库中创建的存储过程都有哪些的语句:

  1. SHOW PROCEDURE STATUS [LIKE 需要匹配的存储过程名称]

查看某个存储过程具体是怎么定义的语句:

  1. SHOW CREATE PROCEDURE 存储过程名称

删除存储过程的语句:

  1. DROP PROCEDURE 存储过程名称

存储过程中的语句

上边在唠叨存储函数中使用到的各种语句,包括变量的使用、判断、循环结构都可以被用在存储过程中,这里就不再赘述了。

存储过程的参数前缀

存储函数强大的一点是,存储过程在定义参数的时候可以选择添加一些前缀,就像是这个样子:

  1. 参数类型 [IN | OUT | INOUT] 参数名 数据类型

可以看到可选的前缀有下边3种:

前缀 实际参数是否必须是变量 描述
IN 用于调用者向存储过程传递数据,如果IN参数在过程中被修改,调用者不可见。
OUT 用于把存储过程运行过程中产生的数据赋值给OUT参数,存储过程执行结束后,调用者可以访问到OUT参数。
INOUT 综合INOUT的特点,既可以用于调用者向存储过程传递数据,也可以用于存放存储过程中产生的数据以供调用者使用。

这么直接描述有些生硬哈,我们来举例子分别仔细分析一下:

  • IN参数

    先定义一个参数前缀是IN的存储过程p_in

    1. mysql> delimiter $
    2. mysql> CREATE PROCEDURE p_in (
    3. -> IN arg INT
    4. -> )
    5. -> BEGIN
    6. -> SELECT arg;
    7. -> SET arg = 123;
    8. -> END $
    9. Query OK, 0 rows affected (0.00 sec)
    10. mysql> delimiter ;
    11. mysql>
  1. 这个`p_in`存储过程只有一个参数`arg`,它的前缀是`IN`。这个存储过程实际执行两个语句,第一个语句是用来读取参数`arg`的值,第二个语句是给参数`arg`赋值。我们调用一下`p_in`
  2. mysql> SET @a = 1;
  3. Query OK, 0 rows affected (0.00 sec)
  4. mysql> CALL p_in(@a);
  5. +------+
  6. | arg |
  7. +------+
  8. | 1 |
  9. +------+
  10. 1 row in set (0.00 sec)
  11. Query OK, 0 rows affected (0.00 sec)
  12. mysql> SELECT @a;
  13. +------+
  14. | @a |
  15. +------+
  16. | 1 |
  17. +------+
  18. 1 row in set (0.00 sec)
  19. mysql>
  20. 我们定义了一个变量`a`并把整数`1`赋值赋值给它,因为它是在客户端定义的,所以需要加`@`前缀,然后把它当作参数传给`p_in`存储过程。从结果中可以看出,第一个读取语句被成功执行,虽然第二个语句没有报错,但是在存储过程执行完毕后,再次查看变量`a`的值却并没有改变,这也就是说:IN参数只能被用于读取,对它赋值是不会被调用者看到的。
  21. 另外,因为我们只是想在存储过程执行中使用IN参数,并不需要把执行过程中产生的数据存储到它里边,所以其实在调用存储过程时,将常量作为参数也是可以的,比如这样:
  22. mysql> CALL p_in(1);
  23. +------+
  24. | arg |
  25. +------+
  26. | 1 |
  27. +------+
  28. 1 row in set (0.00 sec)
  29. Query OK, 0 rows affected (0.00 sec)
  30. mysql>
  • OUT参数

    先定义一个前缀是OUT的存储过程p_out

    1. mysql> delimiter $
    2. mysql> CREATE PROCEDURE p_out (
    3. -> OUT arg INT
    4. -> )
    5. -> BEGIN
    6. -> SELECT arg;
    7. -> SET arg = 123;
    8. -> END $
    9. Query OK, 0 rows affected (0.00 sec)
    10. mysql> delimiter ;
    11. mysql>
  1. 这个`p_out`存储过程只有一个参数`arg`,它的前缀是`OUT``p_out`存储过程也有两个语句,一个用于读取参数`arg`的值,另一个用于为参数`arg`赋值,我们调用一下`p_out`
  2. mysql> SET @b = 2;
  3. Query OK, 0 rows affected (0.00 sec)
  4. mysql> CALL p_out(@b);
  5. +------+
  6. | arg |
  7. +------+
  8. | NULL |
  9. +------+
  10. 1 row in set (0.00 sec)
  11. Query OK, 0 rows affected (0.00 sec)
  12. mysql> SELECT @b;
  13. +------+
  14. | @b |
  15. +------+
  16. | 123 |
  17. +------+
  18. 1 row in set (0.00 sec)
  19. mysql>
  20. 我们定义了一个变量`b`并把整数`2`赋值赋值给它,然后把它当作参数传给`p_out`存储过程。从结果中可以看出,第一个读取语句并没有获取到参数的值,也就是说OUT参数的值默认为`NULL`。在存储过程执行完毕之后,再次读取变量`b`的值,发现它的值已经被设置成`123`,说明在过程中对该变量的赋值对调用者是可见的!这也就是说:OUT参数只能用于赋值,对它赋值是可以被调用者看到的。
  21. 另外,由于`OUT`参数只是为了用于将存储过程执行过程中产生的数据赋值给它后交给调用者查看,那么在调用存储过程时,实际的参数就不允许是常量!
  • INOUT参数

    知道了IN参数和OUT参数的意思,INOUT参数也就明白了,这种参数既可以在存储过程中被读取,也可以被赋值后被调用者看到,所以要求在调用存储过程时实际的参数必须是一个变量,不然还怎么赋值啊!INOUT参数类型就不具体举例子了,大家可以自己试试哈~

需要注意的是,如果我们不写明参数前缀的话,默认的前缀是IN!

由于存储过程可以传入多个OUT或者INOUT类型的参数,所以我们可以在一个存储过程中获得多个结果,比如这样:

  1. mysql> delimiter $
  2. mysql> CREATE PROCEDURE get_score_data(
  3. -> OUT max_score DOUBLE,
  4. -> OUT min_score DOUBLE,
  5. -> OUT avg_score DOUBLE,
  6. -> s VARCHAR(100)
  7. -> )
  8. -> BEGIN
  9. -> SELECT MAX(score), MIN(score), AVG(score) FROM student_score WHERE subject = s INTO max_score, min_score, avg_score;
  10. -> END $
  11. Query OK, 0 rows affected (0.02 sec)
  12. mysql> delimiter ;
  13. mysql>

我们定义的这个get_score_data存储过程接受4个参数,前三个参数都是OUT参数,第四个参数没写前缀,默认就是IN参数。存储过程的内容是将指定学科的最高分、最低分、平均分分别赋值给三个OUT参数。在这个存储过程执行完之后,我们可以通过访问这几个OUT参数来获得相应的最高分、最低分以及平均分:

  1. mysql> CALL get_score_data(@a, @b, @c, '母猪的产后护理');
  2. Query OK, 1 row affected (0.01 sec)
  3. mysql> SELECT @a, @b, @c;
  4. +------+------+------+
  5. | @a | @b | @c |
  6. +------+------+------+
  7. | 100 | 55 | 73 |
  8. +------+------+------+
  9. 1 row in set (0.00 sec)
  10. mysql>

存储过程和存储函数的不同点

存储过程存储函数非常类似,我们列举几个它们的不同点以加深大家的对这两者区别的印象:

  • 存储函数在定义时需要显式用RETURNS语句标明返回的数据类型,而且在函数体中必须使用RETURN语句来显式指定返回的值,存储过程不需要。

  • 存储函数只支持IN参数,而存储过程支持IN参数、OUT参数、和INOUT参数。

  • 存储函数只能返回一个值,而存储过程可以通过设置多个OUT参数或者INOUT参数来返回多个结果。

  • 存储函数执行过程中产生的结果集并不会被显示到客户端,而存储过程执行过程中产生的结果集会被显示到客户端。

  • 存储函数直接在表达式中调用,而存储过程只能通过CALL语句来显式调用。