MySQL入门系列:存储程序(二)之存储函数简介

存储例程

存储例程存储程序的一种类型,本质上也是封装了一些可执行的语句,只不过它的调用方式是:须要手动去调用存储例程又能够分为存储函数存储过程,下边咱们详细唠叨这两个家伙。mysql

存储函数

建立存储函数

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

CREATE FUNCTION 存储函数名称([参数列表])
RETURNS 返回值类型
BEGIN
    函数体内容
END
复制代码

从这里咱们能够看出,定义一个存储函数须要指定函数名称、参数列表、返回值类型以及函数体内容,若是该函数不须要参数,那参数列表能够被省略,函数体内容能够包括一条或多条语句,每条语句都要以分号;结尾。里边的制表符和换行仅仅是为了好看,若是你以为烦,彻底能够用空格代替! 光看定义理解的不深入,咱们先写一个存储函数开开眼:sql

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.00 sec)

mysql> delimiter ;
复制代码

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

存储函数的调用

咱们自定义的函数和系统内置函数的使用方式是同样的,都是在函数名后加小括号()表示函数调用,有参数的函数调用能够把参数写到小括号里边。函数调用能够做为查询对象或者搜索条件,或者和别的操做数一块儿组成更复杂的表达式,咱们如今来调用一下刚刚写好的这个函数吧:bash

mysql> SELECT avg_score('母猪的产后护理');
+------------------------------------+
| avg_score('母猪的产后护理')        |
+------------------------------------+
|                                 73 |
+------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT avg_score('论萨达姆的战争准备');
+------------------------------------------+
| avg_score('论萨达姆的战争准备')          |
+------------------------------------------+
|                                    73.25 |
+------------------------------------------+
1 row in set (0.00 sec)

mysql>
复制代码

这样调用函数就比咱们直接写两个又臭又长的查询语句简单多了。服务器

查看和删除存储函数

若是咱们想查看咱们已经定义了多少个存储函数,可使用下边这个语句:编程语言

SHOW FUNCTION STATUS [LIKE 须要匹配的函数名]
复制代码

因为这个命令获得的结果太多,咱们就不演示了哈,本身试试。函数

若是咱们想查看某个函数的具体定义,可使用这个语句:学习

SHOW CREATE FUNCTION 函数名
复制代码

好比这样:测试

mysql> SHOW CREATE FUNCTION avg_score\G
*************************** 1. row ***************************
            Function: avg_score
            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
     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.01 sec)

mysql>
复制代码

忽略结果中咱们看不懂的那些东东,后边都会详细唠叨的,如今主要聚焦在这个函数的定义处。

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

DROP FUNCTION 函数名
复制代码

好比咱们来删掉avg_score这个函数:

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

mysql>
复制代码

什么?你觉得到这里存储函数就唠叨完了么?写完是不可能的,这辈子都不可能写完的!到如今为止咱们只是勾勒出一个存储函数的大体轮廓,下边咱们来详细说一下MySQL定义函数体时支持的一些语句。

在函数体中定义变量

咱们在前边说过在命令行(黑框框)中自定义变量的方式,它能够不用声明就为变量赋值(也就是调用SET语句)。而在函数体中使用变量前必须先声明这个变量,声明方式以下:

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)

mysql>
复制代码

若是咱们不对声明的变量赋值的话,它的默认值就是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 ;
mysql>
复制代码

在新建立的这个var_default_demo函数中,咱们声明了一个变量c,而且指定了它的默认值为1,而后看一下函数的调用结果:

mysql> SELECT var_default_demo();
+--------------------+
| var_default_demo() |
+--------------------+
|                  1 |
+--------------------+
1 row in set (0.00 sec)

mysql>
复制代码

获得的结果是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 xiaohaizi.avg_score; expected 1, got 0
mysql>
复制代码
判断语句的编写

像其余的编程语言同样,在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> 
复制代码

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

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

固然了,咱们举的这个例子仍是比较白痴的啦,固然了,咱们只是为了说明语法怎么用,等于到更复杂一点的业务逻辑再往复杂了说哈。咱们如今调用一下这个函数:

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>
复制代码
循环语句的编写

除了判断语句,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 ;
    mysql>
    复制代码

    在函数sum_all中,咱们接收一个INT UNSIGNED类型的参数,声明了两个INT类型的变量iresult。咱们先测试一下这个函数:

    mysql> SELECT sum_all(3);
    +------------+
    | sum_all(3) |
    +------------+
    |          6 |
    +------------+
    1 row in set (0.00 sec)
    
    mysql>
    复制代码

    分析一下这个结果是怎么产生的,初始的状况下result的值是0i的值是1,给定的参数n的值是3。这个过程就是:

    1. 先判断i <= n是否成立,显然成立,进入循环体,将result的值设置为1result + i),i的值为2i + 1)。
    2. 再判断i <= n是否成立,显然成立,进入循环体,将result的值设置为3result + i),i的值为3i + 1)。
    3. 再判断i <= n是否成立,显然成立,进入循环体,将result的值设置为6result + i),i的值为4i + 1)。
    4. 再判断i <= n是否成立,显然不成立,退出循环。

    因此最后返回的result的值就是6,也就是123这三个数的和。

  • 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注释语句说明这个函数的做用。

小册

本系列专栏都是MySQL入门知识,想看进阶知识能够到小册中查看:《MySQL是怎样运行的:从根儿上理解MySQL》的连接 。小册的内容主要是从小白的角度出发,用比较通俗的语言讲解关于MySQL进阶的一些核心概念,好比记录、索引、页面、表空间、查询优化、事务和锁等,总共的字数大约是三四十万字,配有上百幅原创插图。主要是想下降普通程序员学习MySQL进阶的难度,让学习曲线更平滑一点~

相关文章
相关标签/搜索