MySQL高效编程学习笔记(十)--函数与触发器
存储函数就是用户自定义函数,触发器是和表关系密切的类似与存储过程的技术。
一、存储函数
- 定义存储函数
CREATE FUNCTION 函数名(参数1 数据类型1,... ,参数n 数据类型n)RETURNS 返回值类型
BEGIN
SQL语句
RETURN 返回值;
END
和存储过程不同点:
1) 存储函数参数只能是输入型IN。
2) 向调用方返回结果值
阶乘函数
DELIMITER //
CREATE FUNCTION fn_factorial(p_num INT) RETURNS INT
BEGIN
DECLARE p_result INT DEFAULT 1;#声明局部变量并赋初值1
REPEAT
SET p_result = p_result * p_num;
SET p_num = p_num - 1;
UNTIL p_num <= 1 END REPEAT;
RETURN p_result;
END
//
DELIMITER ; # DELIMITER后一定要有空格
2. 确认创建成功的存储函数
SHOW FUNCTION STATUS\G
SHOW CREATE FUNCTION fn_factorial \G
3. 调用函数
SELECT fn_factorial(5), fn_factorial(10);
二、触发器
触发器是针对数据库的插入更新删除等事件而被调用的特殊存储过程。
举个栗子,在图书馆借出了一本书后,借出信息表需要插入数据,而信息管理表书籍数减一,若还一本借出信息表需要插入数据,而信息管理表书籍数加一,用触发器可以实现在更新借出信息表时,触发器自动完成对书籍信息管理表的更新,实现针对相关表的处理自动化机制。
-
创建触发器
CREATE TRIGGER 触发器名 发生时刻 事件名
ON 表名 FOR EACH ROW
BEGIN
操作1;
END
触发器特点:
1) 指定成为触发器调用方的表名:即创建触发器时,需要指定针对哪个表的操作。
2) 决定触发运行的时刻:触发器的运行时刻由事件名和发生时刻两个因素决定。
注意:事件名并不一定是严格的INSERT\UPDATE\DELETE命令、比如像LODA DATA INFILE \REPLACE等有数据插入动作的处理都属于INSERT事件类。
发生时刻有两个参数,BEFORE事件发生之前调用触发器, AFTER事件发生之后调用触发器。
FOR EACH ROW意思是触发器以行为单位执行,当用户删除三条记录时,相关的触发器也会被触发三次。(在MySQL中FOR EACH ROW是固定参数,在Oracle或postgresql等数据库中可以以语句为单位)。
注意不要滥用触发器,否则可能只想对一个表操作,而触发好几个表的操作。
- 定义触发器
现有两个表chinacitycode和chinacitycode_history,当我们删除表chinacitycode的数据时,我们想通过触发器将删除的数据备份到chinacitycode_history中
DELIMITER //
CREATE TRIGGER trg_cityhistory AFTER DELETE
ON chinacitycode FOR EACH ROW
BEGIN
INSERT INTO chinacitycode_history(UpdateTime,CityCode, EnglishName, ReadmeName, ChinsesName, Country) VALUES(NOW(),OLD.CityCode, OLD.EnglishName, OLD.ReadmeName, OLD.ChinsesName, OLD.Country);
END
//
DELIMITER ;
触发器中使用关键字OLD/NEW可参照变更前后的数据,如果发生时刻为BEFORE,使用NEW.列名,反之AFTER用OLD.列名,不过也要根据具体事件来确定,比如INSERT可用NEW不能使用OLD,DELETE可用OLD不能使用NEW,UPDATE都可以用。
3. 对触发器基本操作
1)确认创建的触发器
SHOW TRIGGERS\G
2)删除触发器
DROP TRIGGER 触发器名
DROP TRIGGER trg_cityhistory;
3)测试触发器
执行删除表chinacitycode的内容,看chinacitycode_history中数据是否增加。
SELECT * FROM chinacitycode_history;
DELETE FROM chinacitycode WHERE ID < 10;
SELECT * FROM chinacitycode_history;
三、游标
在创建存储过程、存储函数、触发器时,可以使用游标对SELECT获取的结果一件一件处理。游标通过移动指针进行逐行数据处理。下面我们先直接给出一个例子的代码,我们将查到的结果中的ChineseName全部取出,并用逗号分开。
DELIMITER //
CREATE PROCEDURE sp_cursor(OUT result TEXT)
BEGIN
DECLARE flag BIT DEFAULT 0;#定义标志变量flag,判断是否取到最后一条记录
DECLARE temp VARCHAR(20);#声明游标
DECLARE cur CURSOR FOR SELECT ChineseName FROM chinacitycode WHERE ID <=5;#
DECLARE CONTINUE HANDLER FOR NOT FOUND SET flag=1;#定义取出游标例外的处理
OPEN cur;#打开游标
WHILE flag != 1 DO#本循环作用是一条条取出并处理游标内容
FETCH cur INTO temp;#将当前行内容保存到本地
IF flag !=1 THEN
SET result = CONCAT_WS(‘,’,result,temp);
END IF;
END WHILE;
CLOSE cur;
END
//
DELIMITER ;
CALL sp_cursor (@num);
SELECT @num;
- 声明游标
DECLARE 游标名 CURSOR FOR SELECT 语句; - 打开游标
OPEN 游标名; - 从指针中取得记录数据
从打开的游标中获取下一条数据,并将数据保存到指定变量中,
FETCH 游标名 INTO 变量名,…;
打开游标后,指针指向第一条记录开始的位置,后面顺序依次取下一条数据直到完成读取。 - 读取了所有游标记录后的处理
使用循环处理读取的游标内容需决定循环结束条件,循环结束条件一般为指针到最后一行或者不能读取下一行。当FETCH …INTO…命令执行,如果不能读取下一行,会抛出NOT FOUND例外,例外发生时的处理使用DECLARE…HANDLAER FOR命令,格式如下:
DECLARE 处理种类 HANDLER FOR 例外的种类 例外发生的处理
本例中事先定义当NOT FOUND例外发生时,将标志变量设置为1,然后结束循环。
[处理种类]决定完成[例外发生时的处理]后该如何动作,可以指定EXIT立即结束或者CONTINUE继续下面的处理。
FETCH…INTO命令发生例外后,因为变量temp没有得到正确的赋值,因此原来紧接着此语句的[SET p_result = CONCAT_WS(‘,’, p_result,temp);]语句是不应该被执行的,为避免此情况,在前面加入判断语句IF flag!=1 THEN - 关闭游标
游标使用完后需要一一关闭,CLOSE 游标名
Eg:网上的取多字段的例子
delimiter //
create PROCEDURE phoneDeal()
BEGIN
DECLARE id varchar(64); -- id
DECLARE phone1 varchar(16); -- phone
DECLARE password1 varchar(32); -- 密码
DECLARE name1 varchar(64); -- id
-- 遍历数据结束标志
DECLARE done INT DEFAULT FALSE;
-- 游标
DECLARE cur_account CURSOR FOR select phone,password,name from account_temp;
-- 将结束标志绑定到游标
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 打开游标
OPEN cur_account;
-- 遍历
read_loop: LOOP
-- 取值 取多个字段
FETCH NEXT from cur_account INTO phone1,password1,name1;
IF done THEN
LEAVE read_loop;
END IF;
-- 你自己想做的操作
insert into account(id,phone,password,name) value(UUID(),phone1,password1,CONCAT(name1,'的家长'));
END LOOP;
CLOSE cur_account;
END//