• 存储例程


    存储例程

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

    存储函数

    创建存储函数

    存储函数其实就是一种函数,只不过在这个函数里可以执行命令语句而已。函数它可以把处理某个问题的过程封装起来,之后直接调用函数就可以去解决同样的问题了。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 语句来显式调用。
  • 相关阅读:
    javascript小记
    好看的echart的词云效果(wordCloud)
    工作中经常用到的git的简单操作记录
    积累就是提升之浅谈分时函数
    有意思的面试小试题
    分享张鑫旭大神的,纯css打字小技巧,我顺便收藏一下
    模仿也是提高,纯css小技巧实现头部进度条
    推荐好用的css调试工具,两个
    There appears to be trouble with your network connection. Retrying
    Enter passphrase for key ‘/root/.ssh/id_rsa’ : git push 重复输入密码的问题
  • 原文地址:https://www.cnblogs.com/qiuxirufeng/p/12632677.html
Copyright © 2020-2023  润新知