数据库系统笔记 | 函数、存储过程、游标

Table of Contents

 

函数

自定义函数

基本语法

控制语句

存储过程

基本用法

自定义函数和存储过程的区别

游标


函数

函数的意义就在于封装特定的功能单元,写一份函数,可以在多个语句中多次使用,简化编程工作。

数据库系统中的函数大致也可以分为内置函数和自定义函数(UDF),内置函数比如count,sum等等,自定义函数比较灵活。

本文demo所用的数据库版本:MySQL8.0.13,样本数据来自《数据库系统概念》这本书的官网:http://db-book.com/。

参考:https://www.cnblogs.com/caoruiy/p/4485273.html

自定义函数

基本语法

--创建UDF:
    CREATE [AGGREGATE] FUNCTION function_name(parameter_name type,[parameter_name type,...])
    RETURNS {STRING|INTEGER|REAL}
    BEGIN  
      runtime_body --如果函数体有多条语句,放在BEGIN..END中,单条语句不需要BEGIN...END
    END

--删除UDF:
  DROP FUNCTION function_name

--调用UDF:
  SELECT function_name(parameter_value,...)

note:UDF可以有多个参数或者没有参数,但是必须有且仅有一个返回值。

Demo

创建函数:给定系的名称,返回该系的教师人数。

  1 DELIMITER //  --修改默认结束符分号
  2 DROP FUNCTION IF EXISTS dept_count;  --如果函数存在先进行删除
  3 CREATE FUNCTION dept_count(dept_name VARCHAR(20))
  4   RETURNS INTEGER  --声明返回类型
  5 BEGIN
  6   DECLARE tmp_count,d_count INTEGER DEFAULT 0; --这些变量的作用范围是在BEGIN...END程序中,而且定义局部变量语句必须在BEGIN...END的第一行定义
  7   SELECT count(*) INTO tmp_count from instructor where instructor.dept_name = dept_name;
  8   SET d_count = tmp_count; --这里tmp_count是多余的,只是演示一下SET的用法
  9   RETURN d_count;
 10 END//
 11 DELIMITER ;  --恢复默认结束符分号

使用dept_count函数

mysql> select dept_count('Biology');
+-----------------------+
| dept_count('Biology') |
+-----------------------+
|                     2 |
+-----------------------+
1 row in set (0.00 sec)
mysql> select distinct dept_name, dept_count(dept_name) from instructor;
+------------+-----------------------+
| dept_name  | dept_count(dept_name) |
+------------+-----------------------+
| Biology    |                     2 |
| Comp. Sci. |                     4 |
| Elec. Eng. |                     1 |
| Finance    |                     3 |
| History    |                     2 |
| Music      |                     1 |
| Physics    |                     2 |
+------------+-----------------------+
mysql> select distinct dept_name, dept_count(dept_name) from instructor where dept_count(dept_name) > 2;
+------------+-----------------------+
| dept_name  | dept_count(dept_name) |
+------------+-----------------------+
| Comp. Sci. |                     4 |
| Finance    |                     3 |
+------------+-----------------------+

控制语句

数据库系统也支持和通用编程语言类似的各种控制语句,MySQL中可以使用IF语句、CASE语句、LOOP语句、LEAVE语句、ITERATE语句、REPEAT语句和WHILE语句来进行流程控制,在自定义函数中也可以使用这些语句。

IF语句

IF search_condition THEN statement_list --statement_list就是不同条件下的执行语句
[ELSEIF search_condition THEN statement_list] ... 
[ELSE statement_list] 
END IF
  1 DELIMITER //
  2 DROP FUNCTION IF EXISTS dept_count;
  3 CREATE FUNCTION dept_count(dept_name VARCHAR(20))
  4   RETURNS INTEGER
  5 BEGIN
  6   DECLARE tmp_count, d_count INTEGER DEFAULT 0;
  7   SELECT count(*) INTO tmp_count from instructor where instructor.dept_name = dept_nam    e;
  8   IF tmp_count = 0 THEN SET d_count = 100;
  9   ELSE SET d_count = tmp_count;
 10   END IF;
 11   RETURN d_count;
 12 END//
 13 DELIMITER ;
mysql> select dept_count('Math'); --样本数据中没有Math这个系
+--------------------+
| dept_count('Math') |
+--------------------+
|                100 |
+--------------------+

CASE语句

CASE语句和IF语句也用来处理一些条件判断,和IF语句类似。

CASE case_value 
WHEN when_value THEN statement_list 
[WHEN when_value THEN statement_list] ... 
[ELSE statement_list] 
END CASE 

LOOP语句

LOOP语句可以实现循环条件执行。LOOP语句如果不用LEAVE等语句来控制退出条件,就会死循环执行下去,类似于通用编程语言中不带控制条件的for循环语句。

[begin_label:] LOOP 
statement_list 
END LOOP [end_label] 
  1 DELIMITER //
  2 DROP FUNCTION IF EXISTS loop_100;
  3 CREATE FUNCTION loop_100()
  4   RETURNS INTEGER
  5 BEGIN
  6   DECLARE result INTEGER DEFAULT 0;
  7   loop_100: LOOP
  8     SET result=result + 1;
  9     IF result=100 THEN LEAVE loop_100; --满足条件,用LEAVE跳出循环
 10     END IF;
 11   END LOOP loop_100;
 12   RETURN result;
 13 END//
 14 DELIMITER ;
mysql> select loop_100();
+------------+
| loop_100() |
+------------+
|        100 |
+------------+
1 row in set (0.00 sec)

LEAVE语句

LEAVE语句主要用于跳出循环控制,在LOOP语句的demo中已经演示。

LEAVE label 

ITERATE语句

ITERATE语句也是用来跳出循环的语句。但是,ITERATE语句是跳出本次循环,然后直接进入下一次循环,类似于通用编程语言中的continue语句。

TERATE语句只可以出现在LOOP、REPEAT、WHILE语句内。

ITERATE label 

REPEAT语句

REPEAT语句是有条件控制的循环语句。当满足特定条件时,就会跳出循环语句,类似于设置了退出条件的for循环语句。REPEAT语句的基本语法形式如下:

[begin_label:] REPEAT 
statement_list 
UNTIL search_condition 
END REPEAT [end_label]

WHILE语句

WHILE语句也是有条件控制的循环语句。但WHILE语句和REPEAT语句是不一样的。

WHILE语句是当满足条件时,执行循环内的语句。

[begin_label:] WHILE search_condition DO 
statement_list 
END WHILE [end_label] 
  1 DELIMITER //
  2 DROP FUNCTION IF EXISTS while_100;
  3 CREATE FUNCTION while_100()
  4   RETURNS INTEGER
  5 BEGIN
  6   DECLARE result INTEGER DEFAULT 0;
  7   WHILE result < 100 DO
  8     SET result=result + 1;
  9   END WHILE;
 10   RETURN result;
 11 END//
 12 DELIMITER ;
mysql> select while_100();
+-------------+
| while_100() |
+-------------+
|         100 |
+-------------+
1 row in set (0.00 sec)

存储过程

        SQL语句需要先编译然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它,由于可以像通用编程语言那样使用各种控制语句,因此使用存储过程可以实现比较复杂的业务逻辑。

基本用法

创建存储过程

改存储过程查询所有系或者指定系学生的平均cred并将结果存储在临时变量avg_cred中:

-- DEFINER is automatically added by MySQLWorkBench
CREATE DEFINER=`root`@`localhost` PROCEDURE `pcdAvgCredForOneDept`(
	IN  dept      VARCHAR(15),
    OUT avg_cred  DECIMAL(8, 2)
)
    COMMENT 'Compute avg cred'
BEGIN
	-- Declare varibale for avg
    DECLARE avg_tmp DECIMAL(8,2);
    
    -- Get the avg including param 'dept'
    IF dept = 'ALL' THEN
		-- Get avg cred of all departments
        SELECT AVG(tot_cred) FROM student INTO avg_tmp;
	ELSE
		-- Get avg cred of a special department
        SELECT AVG(tot_cred) FROM student WHERE dept_name = dept INTO avg_tmp;
	END IF;
    
    -- And finnal, save to out varibale
    SELECT avg_tmp INTO avg_cred;
    
END

使用存储过程

mysql> CALL pcdAvgCredForOneDept('ALL', @avg_cred);
Query OK, 1 row affected (0.01 sec)

mysql> SELECT @avg_cred;
+-----------+
| @avg_cred |
+-----------+
|     75.29 |
+-----------+
mysql> CALL pcdAvgCredForOneDept('Physics', @avg_cred);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT @avg_cred;
+-----------+
| @avg_cred |
+-----------+
|     34.00 |
+-----------+

Notes:

  1. 如果在CLI客户端创建存储过程,也需要用DELIMETER来声明新的语句分隔符。
  2. 所有MySQL变量都以@开始,用来临时存储数据。
  3. 不能通过一个参数返回多个行和列。
  4. 存储过程的创建和执行权限是分开的,对于普通使用者可能只有执行存储过程的权限,而只有数据库管理员才有创建新的存储过程的权限。

删除存储过程

DROP PROCEDURE avgcredfromstudent IF EXISTS;

Notes:

这里存储过程的例子仅为了demo,实际上这种简单的处理不需要写存储过程。

存储过程的优点

  1. 执行效率高:为存储过程是预编译的,在创建好存储过程后,第一次执行一个存储过程时,优化器对其进行分析优化,并且给出最终被存储在系统表中的执行计划,所以相对于执行多条SQL语句,存储过程执行效率也更高。
  2. 标准组件式编程:存储过程被创建后,可以在程序中被多次调用,而不必重新编写该存储过程的SQL语句,增强了代码复用性。而且数据库专业人员可以随时对存储过程进行修改,对应用程序源代码毫无影响,比如某些表名、列名或者业务逻辑发生变化,只需要修改存储过程的代码,只要入参和返回值没有变化,使用者甚至不需要知道存储过程的变化。
  3. 减少网络流量:针对同一个数据库对象的操作(如查询、修改),如果这一操作所涉及的SQL语句被写成存储过程,那么当在客户计算机上调用该存储过程时,网络中传送的只是该调用语句,从而大大减少网络流量并降低了网络负载。
  4. 安全可控性:通过对执行某一存储过程的权限进行限制,能够实现对相应的数据的访问权限的限制,避免了非授权用户对数据的访问,保证了数据的安全。

自定义函数和存储过程的区别

前面讨论的函数和存储过程非常类似,但它们之间还是有以下区别:

  1. 存储过程实现的过程复杂一些,而函数的针对性更强,或者说函数一般用于完成某个特定的运算,而存储过程往往包含了更为复杂的业务逻辑,作为较为独立的业务功能。
  2. 存储过程可以有多个返回值,而自定义函数只有一个返回值。
  3. 存储过程一般独立的执行,自定义函数往往包含在SQL语句中使用。

游标

注:MySQL游标只能用于存储过程。

在查询数据库时,SELECT语句返回的是结果集,而有时候我们需要逐行(或者多行)的处理数据,比如在交互式应用中,

用户需要滚动屏幕上的数据,并对数据进行浏览或修改。

游标是一个存储在MySQL服务器上的数据库查询,它不是SELECT语句,而是SELECT语句检索出来的结果。

使用游标:

  1. 在使用游标之前必须先用DECLARE定义,这个过程实际上没有检索数据,只是定义要使用的SELECT语句。
  2. 用OPEN打开游标后可以使用,这个过程用前面定义的SELECT语句把数据检索出来。
  3. 对于填有数据的游标,根据需要使用FETCH来访问每一行结果。
  4. 在游标使用结束后,必须用CLOSE关闭游标。CLOSE释放游标使用的所有内部内存和资源,因此在每个游标不再需要时都应该关闭。如果不明确关闭游标,MySQL将会在到达END语句时自动关闭它。

下面的例子在前面的存储过程(pcdAvgCredForOneDept)的基础上添加游标的使用:查询学校各个系学生的平均cred,并把结果写入新表avgcredtotals:

CREATE DEFINER=`root`@`localhost` PROCEDURE `pcdAvgCredForEveryDept`()
BEGIN
	-- Declare local variables
    DECLARE done BOOLEAN DEFAULT 0;
    DECLARE o    VARCHAR(15);
    DECLARE t    DECIMAL(8, 2);
    
    -- Declare the cursor
    DECLARE deptcursor CURSOR
    FOR
    SELECT dept_name FROM department;
    
    -- Declare continue handler
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
    
    -- Create a table to store the results
    CREATE TABLE IF NOT EXISTS avgcredtotals
		(dept VARCHAR(15), avg_cred DECIMAL(8, 2));
        
	-- Clear the old data in table
    DELETE FROM avgcredtotals;
    
	-- Open the cursor
    OPEN deptcursor;
    
    -- Loop through all rows
    REPEAT
		-- Get one department
        FETCH deptcursor INTO o;
        
        -- Get the avg cred for this department
        CALL pcdAvgCredForOneDept(o, t);
        
        -- Insert department and avg cred into avgcredtotals
        INSERT INTO avgcredtotals(dept, avg_cred)
        VALUES(o, t);
        
        -- End of loop
        UNTIL done END REPEAT;
        
        -- Close the cursor
        CLOSE deptcursor;
END

通过调用存储过程来使用游标:

mysql> CALL pcdAvgCredForEveryDept;
Query OK, 1 row affected (0.07 sec)

mysql> select * from avgcredtotals;
+------------+----------+
| dept       | avg_cred |
+------------+----------+
| Biology    |   120.00 |
| Comp. Sci. |    89.20 |
| Elec. Eng. |    79.00 |
| Finance    |   110.00 |
| History    |    80.00 |
| Music      |    38.00 |
| Physics    |    34.00 |
| Physics    |    34.00 |
+------------+----------+

Notes:

  1. 这里使用了一个CONTINUE HANDLER,它是在条件出现时被执行的代码。
  2. SQLSTATE '02000'是一个未找到条件,当FETCH访问到结果集的末尾,没有更多行访问时,该条件出现。

你可能感兴趣的:(数据库系统笔记)