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:
删除存储过程
DROP PROCEDURE avgcredfromstudent IF EXISTS;
Notes:
这里存储过程的例子仅为了demo,实际上这种简单的处理不需要写存储过程。
存储过程的优点:
前面讨论的函数和存储过程非常类似,但它们之间还是有以下区别:
注:MySQL游标只能用于存储过程。
在查询数据库时,SELECT语句返回的是结果集,而有时候我们需要逐行(或者多行)的处理数据,比如在交互式应用中,
用户需要滚动屏幕上的数据,并对数据进行浏览或修改。
游标是一个存储在MySQL服务器上的数据库查询,它不是SELECT语句,而是SELECT语句检索出来的结果。
使用游标:
下面的例子在前面的存储过程(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: