mysql自定义函数、存储过程、游标、触发器、事件(定时任务)

MYSQL 专栏收录该内容
31 篇文章 0 订阅

创建函数FUN_GET_ORG_CHILD(rootId)

CREATE  FUNCTION `FUN_GET_ORG_CHILD`(rootId varchar(100))

RETURNS varchar(2000) CHARSET utf8mb4//设置返回值类型
BEGIN   
DECLARE sTemp varchar(2000);  //定义局部变量
DECLARE sTempChd varchar(1000); //定义局部变量
SET sTemp = '$';   //变量赋值
SET sTempChd = rootId; //变量赋值 
WHILE sTempChd is not null and sTempChd <> '0' DO   //while循环
    SET sTemp = concat(sTemp, ',', sTempChd);   
    SELECT group_concat(ORG_ID) INTO sTempChd FROM T_SYS_ORG where FIND_IN_SET(PARENT_ID, sTempChd)>0;   
END WHILE;   
RETURN sTemp;   
END

此sql:SELECT * FROM T_SYS_ORG where FIND_IN_SET(ORG_ID, FUN_GET_ORG_CHILD(#{orgId}))  这样就能查询某个机构的本下级信息了。简单记录一下

扩展一:DEFINER

扩展二:select *  into   变量A  from  table_name     或者 select * from table_name into 变量A

              将查出的结果赋值给变量A

MySQL自定义函数:

mysql 自定义函数(user-defined function ) 简称UDF 是对mysql功能的一个扩展。

语法:    


DELIMITER $$
CREATE FUNCTION 函数名(变量名1 数据类型1,变量名2 数据类型2,...) RETURNS 返回值类型
BEGIN
变量的声明
sql语句
RETURN 值; 
END $$
DELIMITER;

/*
注意:
1、参数 包含两部分:参数名 参数类型
2、函数体:肯定会有return语句,如果没有会报错
     如果return语句没有放在函数体的最后也不报错,但不建议
3、函数体中仅有一句话,则可以省略begin end
4、使用 delimiter语句设置结束标记
*/

 简单示例:统计user表的用户数量

DELIMITER //
CREATE FUNCTION countUser() RETURNS INT
BEGIN
DECLARE total int default 0;
select count(*) into total from user;
return total;
END //
DELIMITER;

1、DELIMITER 定界符

      需要定义定界符,否则是创建不了函数的,因为mysql见到'分号'就认为执行结束了,只有开始

创建时定义分界符,结束时在配对一个分界符,mysql认为这个时候才结束,使得函数能够完整编译创建

附一个详细讲delimiter的博客:MySql中 delimiter 详解

2、变量的声明

                MYSQL中,变量分为三类:局部变量,用户变量、系统变量(会话变量,全局变量)

      局部变量:      

                    (存储过程中)通过DECLARE可以定义一个局部变量,default设置默认值。

                    declare声明变量必须放在最前面(可能是语法要求吧:declare声明的变量要放在begin...end块的第一行)

                    作用范围:BEGIN...END块中

                    语法:DECLARE 变量1[,变量2,... ]变量类型 [DEFAULT 默认值]

                    多个同类型的变量 可以分开或者一起声明   DECLARE total,total1,total2 VARCHAR(10) default null;

                    赋值:通过set或者 select * into 变量 from table 或者 select * from table into 变量

       用户变量:                  

                    用户变量不用事前申明,在用的时候直接用“@变量名”使用即可。

                    第一种用法:set @num=1; 或set @num:=1; //这里要使用set语句创建并初始化变量,直接使用@num变量

                    第二种用法:select @num:=1; 或 select @num:=字段名 from 表名 where …

                    注意上面两种赋值符号,使用set时可以用“=”或“:=”,但是使用select时必须用“:=赋值”

                    作用范围:在客户端链接到数据库实例整个过程中都是有效的。     

       系统变量

                  会话变量

                                仅对当前客户端连接有效,
                                使用set session varname=value;或者set @@session.varname=value;设置会话级别的系统变量
                                或者使用set local varname=value;或者set @@local.varname=value;设置会话级别的系统变量
                                其中,修改会话变量不需要root权限,且session可以省略,如:set varname=value;

                                查看所有的会话变量:
                                                                   SHOW [SESSION] VARIABLES;
                                查看一个会话变量也有如下三种方式:

                                                                    select @@var_name;

                                                             select @@session.var_name;

                                                             show session variables like “%var%”;

                全局变量

                                使用set global varname=value;或者set @@global.varname=value;设置全局系统变量的值
                                其中,修改全局变量时需要root权限,且global不可省略。

                                查看所有的全局变量:
                                                                  show global variables;
                                要想查看一个全局变量,有如下两种方式:
                                                                  select @@global.var_name;
                                                                  show global variables like “%var%”;

3、函数

      存储过程和函数区别: 存储没有返回值,使用输出参数返回数据,可以有多个,函数有返回值,只能返回一个。

字符串函数

CHARSET(str) //返回字串字符集
CONCAT (string2 [,... ]) //连接字串
INSTR (string ,substring ) //返回substring首次在string中出现的位置,不存在返回0
LCASE (string2 ) //转换成小写
LEFT (string2 ,length ) //从string2中的左边起取length个字符
LENGTH (string ) //string长度,字节的长度
LOAD_FILE (file_name ) //从文件读取内容
LOCATE (substring , string [,start_position ] ) 同INSTR,但可指定开始位置
LPAD (string ,length ,pad ) //重复用pad加在string开头,直到字串长度为length
LTRIM (string ) //去除前端空格
REPEAT (string2 ,count ) //重复count次
REPLACE (str ,search_str ,replace_str ) //在str中用replace_str替换search_str
RPAD (string ,length ,pad) //在str后用pad补充,直到长度为length
RTRIM (string ) //去除后端空格
STRCMP (string1 ,string2) //逐字符比较两字串大小,
SUBSTRING (str , position [,length ]) //从str的position开始,取length个字符,
TRIM([[BOTH|LEADING|TRAILING] [padding] FROM]string2) //去除指定位置的指定字符
UCASE (string2 ) //转换成大写
RIGHT(string2,length) //取string2最后length个字符
SPACE(count) //生成count个空格
注:mysql中处理字符串时,默认第一个字符下标为1,即参数position必须大于等于1

 数学函数

ABS (number2 ) //绝对值
BIN (decimal_number) //十进制转二进制
CEILING (number2 ) //向上取整
CONV(number2,from_base,to_base) //进制转换
FLOOR (number2 ) //向下取整
FORMAT (number,decimal_places ) //保留小数位数
HEX (DecimalNumber) //转十六进制
POWER (number ,power ) //求指数
RAND([seed]) //随机数
ROUND (number [,decimals ]) //四舍五入,decimals为小数位数] 注:返回类型并非均为整数

日期函数

ADDTIME (date2 ,time_interval) //将time_interval天加到date2 秒
ADDDATE(‘2019-04-15’,2); #2天
CURRENT_DATE ( ) //当前日期
CURRENT_TIME ( ) //当前时间
CURRENT_TIMESTAMP ( ) //当前时间戳
DATE (datetime ) //返回datetime的日期部分
DATE_ADD (date2 , INTERVAL d_value d_type ) //在date2中加上日期或时间
DATE_FORMAT (datetime ,FormatCodes ) //使用formatcodes格式显示datetime
DATE_SUB (date2 , INTERVAL d_value d_type ) //在date2上减去一个时间
DATEDIFF (date1 ,date2 ) //两个日期差
DAY (date ) //返回日期的天
DAYNAME (date ) //英文星期
DAYOFWEEK (date ) //星期(1-7) ,1为星期天
DAYOFYEAR (date ) //一年中的第几天
EXTRACT (interval_name FROM date ) //从date中提取日期的指定部分
MAKEDATE (year ,day ) //给出年及年中的第几天,生成日期串
MAKETIME (hour ,minute ,second ) //生成时间串
MONTHNAME (date ) //英文月份名
NOW ( ) //当前时间
SEC_TO_TIME (seconds ) //秒数转成时间
STR_TO_DATE (string ,format ) //字串转成时间,以format格式显示
TIMEDIFF (datetime1 ,datetime2 ) //两个时间差
TIME_TO_SEC(time) //时间转秒数
WEEK (date_time [,start_of_week ]) //第几周
YEAR (datetime ) //年份
DAYOFMONTH(datetime) //月的第几天
HOUR(datetime) //小时

4、函数中常用的流程控制语句

  • if
  • case
  • loop
  • leave
  • iterate
  • repeat
  • while

    每个流程中可能包含一个单独语句,或者是使用BEGIN...END构造的复合语句,构造可以被嵌套

  a)if语句

      语法:   类似java中的   if...elseif...else....

if   条件1    then    ...;
elseif   条件2    then    ...;
else ...;
end if;

重点:有 if 则必有 end if    if语句可用在begin...end 中或外边。 

 if示例:

DROP FUNCTION IF EXISTS testIf;//判断函数是否存在  存在则删除
CREATE FUNCTION `testIf`(age INT) RETURNS VARCHAR(100)//固定写法  创建函数  定义返回值类型
BEGIN //固定语法   begin ... end 块
#DECLARE a INT DEFAULT 0; //declare声明变量  俗称 局部变量
#DECLARE message VARCHAR(100) DEFAULT '';//declare声明变量
#IF a < 18 then SET message = "你的age小于18";
#elseif a > 60 THEN SET message ="你的age大于60";
#else SET message = "age处于18和60之间";
#end if;//if语句的结束   固定写法
DECLARE message VARCHAR(100) DEFAULT '';
SET @a = age; // 俗称  用户变量  set声明
IF @a < 18 THEN SET message = "你的age小于18";
ELSEIF @a > 60 THEN SET message ="你的age大于60";
ELSE SET message = "age处于18和60之间";
END IF;
RETURN message;//返回值
END

b)case语句

语法:

语法一:
    case  
    when  条件1  then  ...;
    when  条件2  then ...;
    else ...;
    end case;
语法二:
    case 变量或表达式
    when 值1 then 语句1;
    when 值2 then 语句2;
    ...;
    else 语句n;
    end case;

 有case 必有end case            ,case语句可应用在begin  end 中外

示例:

DROP FUNCTION IF EXISTS testCase;
CREATE FUNCTION `testCase`(age INT) RETURNS VARCHAR(100)
BEGIN
DECLARE message VARCHAR(100) DEFAULT '';
SET @a = age;
CASE WHEN @a < 18 THEN SET message = "你的age小于18";
WHEN @a > 60 THEN SET message ="你的age大于60";
ELSE SET message = "age处于18和60之间";
END CASE;//case的结束  固定写法
RETURN CONCAT('你输入的age是',@a,',',message);
END

c)loop和leave语句

LOOP语句可以使某些特定的语句重复执行,实现一个简单的循环。但是LOOP语句本身没有停止循环的语句,必须是遇到LEAVE语句等才能停止循环。

loop语法格式:

[begin_label:] LOOP 
statement_list 
END LOOP [end_label] 

begin_label参数和end_label参数分别表示循环开始和结束的标志,这两个标志必须相同,而且都可以省略;statement_list参数表示需要循环执行的语句。

特点:

  1. loop相当于java中的while(true){ }
  2. 有loop  必有  end loop

简单的loop示例:

add_num: LOOP  
SET @count=@count+1;  
END LOOP add_num ; 

由于loop没有停止循环的语句,所以此loop示例是一个死循环,要想停止循环,需要借助leave语句来跳出循环

//如果变量@count的值等于100  则停止循环
add_num: LOOP 
SET @count=@count+1; 
IF @count=100 THEN LEAVE add_num; 
END IF;
END LOOP add_num ; 

简单示例:

DROP FUNCTION IF EXISTS testLoopAndLeave;
CREATE FUNCTION testLoopAndLeave() RETURNS VARCHAR(1000)
BEGIN
DECLARE TOTAL VARCHAR(1000) DEFAULT '';//存放loop循环中@count值的变化
SET @count= 1;//声明变量@count
add_count:LOOP
SET @count= @count+FLOOR(RAND()*10);//@count每次加上一个随机数
SET TOTAL = CONCAT(TOTAL,',',@count);//记录@count值循环中的变化
IF @count > 1000 THEN LEAVE add_count;//@count大于1000则停止loop循环
END IF;
END LOOP add_count;
RETURN total;
END

d)repeat语句

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

[begin_label:] REPEAT 
statement_list; //循环执行体
UNTIL search_condition //满足条件结束体   注意:until结束没有分号(容易加分号报错)  
END REPEAT [end_label];  //begin_label end_label  开始结束标识位  两者一样哈

特点:

  1. 先执行后判断 相当于java 中的do...while...语句
  2. 有repeat  必有 end repeat
  3. until  后写的时候不要加分号
  4. begin_lable和end_label 用到leave或iterate时需要加上,否则可省略

简单示例:

DROP FUNCTION IF EXISTS testRepeat;
CREATE FUNCTION testRepeat() RETURNS VARCHAR(1000)
BEGIN
	DECLARE d VARCHAR(1000) DEFAULT '0';//创建变量d,记录@a的变化过程
	SET @a=0;//声明变量a
my_repeat:REPEAT
	SET @a=@a+1;//repeat的循环体  每次加1
	SET d = CONCAT(d,'-->',@a);//d变量记录
	UNTIL @a =100 //repeat有条件循环的结束条件 满足则停止循环   此语句后没有分号
	END REPEAT my_repeat;
RETURN d;
END

e)while语句

while语句也是有条件控制的循环语句。但while语句和repeat语句是不一样的,while语句是当满足条件时,执行循环内的语句,而repeat是先循环,满足until条件停止循环。

while语法:

[begin_label:] WHILE 条件 DO 
满足条件的执行体
END WHILE [end_label] 

注意:

  1. while是先判断后执行。 
  2. 有while  必有 end while   
  3. 当需要用到 leave和iterate时,则需要加上begin_lable和end_lable,否则可省略不写。

简单示例:

DROP FUNCTION IF EXISTS testWhile;
CREATE FUNCTION testWhile() RETURNS VARCHAR(1000)
BEGIN
DECLARE detail VARCHAR(1000) DEFAULT '0';
SET @num = 0;
while @num < 50 DO
set @num = @num + 2;
SET detail = CONCAT(detail,'>',@num);
END WHILE;
RETURN detail;
END

f)iterate语句

iterate语句也是用来跳出循环的语句。但是,iterate语句是跳出本次循环,然后直接进入下一次循环,iterate语句只可以出现在LOOP、REPEAT、WHILE语句内。

    语法格式:iterate lable                lable 是标识位  即前面所说的  begin_lable 和end_lable 

简单示例:

DROP FUNCTION IF EXISTS  testLeaveAndIterate;
CREATE FUNCTION testLeaveAndIterate() RETURNS VARCHAR(1000)
BEGIN
DECLARE msg VARCHAR(1000) DEFAULT '1';
set @age = 0;
add_loop:LOOP
SET @age = @age + 1;
IF @age = 100 THEN LEAVE add_loop;
ELSEIF MOD(@age,2) = 0 THEN  ITERATE add_loop;
ELSE SET msg = CONCAT(msg,',',@age);
END IF;
END LOOP add_loop;
return msg;
END

小结:

  1. leave与iterate的区别:leave等同于java中的break   iterate等同于java中的continue。
  2. 有if 必有end  if  有case 必有 end case 有loop 必有 end loop  有repeat 必有end repeat  有while  必有end while
  3. declare声明的变量要放在begin...end块的第一行
  4. 写repeat语句时   until后的语句结束不要带分号(自己写的时候带分号 一直报错...看了好久才发现)

删除函数:DROP FUNCTION function_name

调用函数:SELECT function_name(parameter_value,...)

查看函数:SHOW FUNCTION STATUS

MYSQL存储过程

什么是存储过程?

     简而言之,一组sql语句集,功能强大,可以实现比较复杂的逻辑功能。

     存储过程和触发器有点类似,都是一组SQL集,但是存储过程是主动调用的,且功能比触发器更加强大,触发器是某件事触发后自动调用。

优点:

  • 提高代码的重用性
  • 简化操作
  • 减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率

存储过程的语法

1、创建:

CREATE PROCEDURE 存储过程名(参数列表)
BEGIN
	 # 存储过程体(一组合法的SQL语句)
END

2、参数列表

1) 参数列表包含三部分
参数模式 参数名 参数类型

in stuname varchar(20)

也可以写成stuname varchar(20) 但最好把 in 加上。
2) 参数模式

in:该参数可以作为输入,也就是该参数需要调用方传入值。
out:该参数可以作为输出,也就是该参数可以作为返回值。
inout:该参数既可以作为输入又可以作为输出,也就是该参数既需要传入值,又可以返回值。

3、调用

CALL 存储过程名(实参列表);

4、删除

如果存在该存储过程 则删除该存储过程。

drop procedure if exists 存储过程名称

重点:存储过程体中的每条sql语句的结尾要求必须加分号

注意:如果存储过程体仅仅只有一句话,begin end可以省略。

注意:存储过程的结尾可以使用 delimiter 重新设置(一般如果存储过程中存在多个分号结尾,就可以使用delimiter)

5、mysql自定义函数所说的流程控制语句同样适用mysql的存储过程,不在叙述......

存储过程示例:

1、无参列表存储过程

DROP PROCEDURE if EXISTS test1;
CREATE  PROCEDURE `test1`()
BEGIN
INSERT INTO test_user(id,name) VALUES(UUID(),'name1'),
																			(UUID(),'name2'),
																			(UUID(),'name3'),
																			(UUID(),'name4'),
																			(UUID(),'name5'),
																			(UUID(),'name6');

END

看一个实际开发中遇到一个运用MYSQL存储过程实现按天建表的例子:

DROP PROCEDURE IF EXISTS my_test;
CREATE  PROCEDURE `my_test`(IN startDate VARCHAR(10),IN dateNum INT)
BEGIN
     declare k int;
	   set k=0;
		 SET @dateNum = dateNum; 
		 SET @startDate = startDate;
     while k < dateNum do
     set @num =k;
     set @dateName=DATE_ADD(startDate,INTERVAL @num DAY);
     set @tname=concat('my_test',DATE_FORMAT(@dateName,'%Y%m%d'));
     set @dwhe="(`id` bigint(11) NOT NULL AUTO_INCREMENT,
                 `ipaddr` varchar(50) DEFAULT NULL COMMENT '',
				 `ipaddrlong` decimal(50,0) DEFAULT NULL COMMENT '',
				 `domain` varchar(255) DEFAULT NULL COMMENT '',
				 `firstdomain` varchar(255) DEFAULT NULL COMMENT '',
				 `first_time` datetime DEFAULT NULL COMMENT '',
				 `last_time` datetime DEFAULT NULL COMMENT '',
				 `visits_count` varchar(20) DEFAULT NULL COMMENT '',
				 `create_time` timestamp NULL DEFAULT NULL COMMENT '',
				 `update_time` timestamp NULL DEFAULT NULL COMMENT '',
				 `is_check` char(1) DEFAULT NULL COMMENT '',
				 PRIMARY KEY (`id`),
				 UNIQUE KEY `ipaddrlong` (`ipaddr`,`firstdomain`) USING BTREE,
				 KEY `domain_idx` (`domain`) USING BTREE,
				 KEY `last_time_idx` (`last_time`) USING BTREE
				) ENGINE=InnoDB AUTO_INCREMENT=3232405 DEFAULT CHARSET=utf8 COMMENT='';
				";
     set @sql_text:=concat('create table IF NOT EXISTS ',@tname,@dwhe);
     prepare stmt from@sql_text;
     execute stmt;
     set k=k+1;
     end while;
END

 引入这个例子除了看下存储过程,其实想记录下这个东西:

prepare stmt from@sql_text;
execute stmt;

 了解下mysql 存储过程中的 prepare语句:mysql 存储过程中的 prepare语句_嗯哼的博客-CSDN博客

2、带IN模式参数的存储过程

DROP PROCEDURE IF EXISTS testIn;
CREATE PROCEDURE testIn(IN myName VARCHAR(20))
BEGIN
select * from test_user where name = myName;
END

#调用
CALL testIn('name1');

注意如果传参带有中文,如果上面这样会报字符转换错误,需要将VARCHAR(20)改成NVARCHAR(20)

3、模拟用户登陆成功与失败

DROP PROCEDURE IF EXISTS testUserLogin;
CREATE PROCEDURE testUserLogin(IN username VARCHAR(20),IN pwd VARCHAR(20))
BEGIN
DECLARE result INT DEFAULT 0;
SELECT COUNT(1) FROM test_user WHERE name = username and password=pwd INTO result;
SELECT IF(result > 0,'登陆成功','登陆失败') as result;
END
#调用
CALL testUserLogin('name1','123456');

   调用结果:     

4、 带IN   OUT模式的存储过程

DROP PROCEDURE IF EXISTS testInOut;
CREATE PROCEDURE testInOut(IN inId VARCHAR(64),OUT userName VARCHAR(64),OUT pwd VARCHAR(64))
BEGIN
SELECT name,password FROM test_user where id = inId into userName,pwd;
END
#调用
CALL testInOut('d03e536d-f747-11ea-997c-00ff5f20e2ec',@name,@pwd);
#查询 函数返回值 userName 和 pwd
SELECT @name as name,@pwd as password;

@name和@pwd的查询结果:

 5、INOUT模式的存储过程

DROP PROCEDURE IF EXISTS testInOut;
CREATE PROCEDURE testInOut(INOUT a INT,INOUT b INT)
BEGIN
SET a=a*2;
SET b=b*3;
END
#调用
SET @m=10;
SET @n=10;
CALL testInOut(@m,@n);
#查询返回值
SELECT @m,@n;

查询返回结果:

mysql自定义函数和存储过程 都是完成特定功能的SQL集合,二者的区别?

区别:

  1. 调用不一样   函数:select 函数名(参数)   存储过程:call 存储名(参数) 或者 call.存储名(参数)。
  2. 参数:存储过程有IN  OUT  INOUT  三种模式的参数 而自定义函数只有IN类的参数。
  3. 返回值:存储过程可以有多个返回值,而函数只能有一个且函数声明的过程中 需要定义返回值类型和写return语句,而存储过程不需要。
  4. 存储过程一般单独执行而自定义函数往往作为其他sql的一部分去使用。
  5. 存储过程可以有select  update delete  insert  create 语句和一些换算 而mysql自定义函数只能有select语句和一些换算。所以存储过程的功能比较强大。 
  6. 存储过程,可以使用非确定函数,不允许在用户定义函数主体中内置非确定函数(这个网上看的,后面这句不是很理解)

mysql游标

1、什么是游标?

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

2、 游标什么作用?

SELECT name,age from person where age>10;

 在实际开发中后台代码调用这个sql语句,一般会返回一个List<Object>对象,进而通过java的遍历对这个集合做一系列的操作。那么在MYSQL中也有这么一个东西可以储存集合,这个东西就是游标

注意:MySQL游标只能用于存储过程(和函数)。游标主要用于交互式应用。

3、游标的使用:

  1. 声明游标 :这个过程实际上是没有遍历数据的,它只是定义要使用的select语句来获取数据。
  2. 打开游标 : 上面定义好后,那么这里就需要打开游标。这个过程用前面定义的select语句把数据实际检索出来。即这个步骤之后,我们就可以遍历游标中的数据了。
  3. 遍历数据 : 对于有数据的游标,根据需要取出各行的数据来进行一定的操作。
  4. 关闭游标 : 使用完游标后,一定要关闭游标。

4、游标的语法

      a)声明游标

DECLARE cursor_name CURSOR FOR select_statement

     cursor_name是游标名称,在一个块中游标名称是唯一的。select_statement是一个select语句(select语句不能有into子句)

      b)打开游标

OPEN cursor_name ;

      c)遍历游标

FETCH cursor_name INTO var_name ;

   这个语句是获取游标当前指向的数据行,并将指针指向下一行。var_name 是定义的变量,此遍历是将select_statement中查询字段赋值给var_name

     此处说遍历游标不合适,真正遍历游标是mysql自定义函数中的  loop或者repeat或者while循环语句。

     d)关闭游标

CLOSE cursor_name ;

下面举例子来细品游标的使用:

有这么一张表

\

现在需要统计下用户名称包含test出现的次数:

DROP PROCEDURE IF EXISTS countUserLikeName;
CREATE PROCEDURE countUserLikeName(IN param VARCHAR(64))
BEGIN
#n 存的是每次遍历的用户名称
declare n varchar(20);
# 拼装所有的满足查询的用户名称
DECLARE allName VARCHAR(1000) DEFAULT '$';
#满足查询条件的信息数量
DECLARE total INT DEFAULT 0;
# 创建结束标志变量
DECLARE done int default false;
#声明游标
DECLARE myCursor CURSOR FOR SELECT name from  test_user where name like CONCAT('%',param,'%');
# 指定游标循环结束时的返回值
DECLARE continue HANDLER for not found set done = true; #此处这个玩意可能不懂??,后面会说
#total变量初始化
SET total=0;
#打开游标
OPEN myCursor;
#采用loop去遍历游标
add_loop:LOOP  #loop遍历
# 根据游标当前指向的一条数据 插入到上面申明的局部变量中
	fetch myCursor into n;
	# 判断游标的循环是否结束
	if done then  #if语句
		leave add_loop;	# leave跳出游标循环
	end if;  #有if  必有  end if
SET allName=CONCAT(allName,',',n);  # 将满足查询条件的用户名称拼接到一起
SET total = total + 1;  #统计满足查询条件的用户数量
END LOOP; #有loop 必有 end loop
#关闭游标
close myCursor; 
# 输出结果即存储过程的返回值
select allName,total;
END
#调用存储过程
CALL countUserLikeName('test');

调用结果:

 虽然这样的结果用mysql的group by  + group_concat就可以统计出来,这不是重点,只是想展示下游标的遍历。

强调下 fetch:

是获取游标当前指向的数据行,并将指针指向下一行,当游标已经指向最后一行时继续执行会造成游标溢出。
使用loop循环游标时,他本身是不会监控是否到最后一条数据了,像下面代码这种写法,就会造成死循环;

read_loop:loop
fetch cur into n;
set total = total + 1;
end loop;

在MySql中,造成游标溢出时会引发mysql预定义的NOT FOUND错误,所以在上面使用下面的代码指定了当引发not found错误时定义一个continue 的事件,指定这个事件发生时修改done变量的值。

# 指定游标循环结束时的返回值
DECLARE continue HANDLER for not found set done = true; #此处这个玩意可能不懂??,后面会说

所以在loop循环中加上

if done then  #if语句
		leave add_loop;	# leave跳出游标循环
	end if;  #有if  必有  end if

如果done的值是true,就结束循环。继续执行下面的代码。

MYSQL触发器

1、触发器概念

触发器(trigger)是MySQL提供给程序员和数据分析员来保证数据完整性的一种方法,它是与表事件相关的特殊的存储过程,它的执行不是由程序调用,也不是手工启动,而是由事件来触发,比如当对一个表进行操作(insert,delete, update)时就会激活它执行。——百度百科

简而言之,就是一个 insert、update、delete这样的sql语句会触发触发器执行。触发器是特殊的存储过程,与存储过程的区别是存储过程的执行需要被调用而触发器是靠事件的触发去执行,除了语法上不一样,begin.......end块中都是一样的东西。

2、触发器语法

CREATE TRIGGER  <触发器名> <BEFORE|AFTER>  <INSERT|UPDATE|DELETE> ON <表名> FOR EACH Row
BEGIN

<触发器主体>

END

触发器四个点:

  1. 监视对象--------table
  2. 监视事件--------table的update、insert、delete
  3. 触发时间--------before(操作前)、after(操作后)
  4. 触发事件--------update、insert、delete

注意事项:

  1. 触发器绑定的实质是表中的每一行,当某一行发生update、insert、delete操作就会触发触发器。
  2. 如果 before 触发器失败或者语句本身失败,将不执行 after 触发器(如果有的话)
  3. 触发器名称对于一个table来说是唯一的,跨表或者跨库可以有相同名称的触发器
  4. 每一个table最多支持6个触发器: before/update、after/update、before/insert、after/insert、before/delete、after/delete

3、触发器的引用行变量

触发器针对的是数据库中的每一行记录,每行数据在操作前后都会有一个对应的状态,触发器将没有操作之前的状态保存到 old 关键字中,将操作后的状态保存到 new 中

语法:old/new.字段名

  • 在触发目标上执行insert操作后会有一个新行,如果在触发事件中需要用到这个新行的变量,可以用new关键字表示 
  • 在触发目标上执行delete操作后会有一个旧行,如果在触发事件中需要用到这个旧行的变量,可以用old关键字表示 
  • 在触发目标上执行update操作后原纪录是旧行,新记录是新行,可以使用new和old关键字来分别操作
      触发器类型                                                         new和old的使用
INSERT型触发器没有 old,只有 new,new 表示将要(插入前)或者已经增加(插入后)的数据
UPDATE型触发器既有 old 也有 new,old 表示更新之前的数据,new 表示更新之后的数据
DELETE型触发器没有 new,只有 old,old 表示将要(删除前)或者已经被删除(删除后)的数据

示例展示:

    业务需求:用户增删改的时候,产生操作日志信息

      涉及到表:用户表(user)、日志表(user_log)、日志详情表(user_log_detail)

      新增删除用户的时候要在user_log插入对应的日志信息。修改的 时候也要在user_log中插入一条日志信息,且在 user_log_detail插入详情信息(即修改字段、修改前什么值、修改后什么值)

表结构如下:

##用户表
CREATE TABLE `user` (
  `id` varchar(64) NOT NULL,
  `name` varchar(64) DEFAULT NULL,
  `phone` varchar(64) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

##日志表
CREATE TABLE `user_log` (
  `id` varchar(64) NOT NULL,
  `user_id` varchar(255) DEFAULT NULL,
  `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作类型',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

##日志详情表
CREATE TABLE `user_log_detail` (
  `id` varchar(64) NOT NULL,
  `user_log_id` varchar(255) DEFAULT NULL,
  `col` varchar(255) DEFAULT NULL COMMENT '列名称',
  `old` varchar(255) DEFAULT NULL COMMENT '修改前字段值',
  `new` varchar(255) DEFAULT NULL COMMENT '修改后字段值'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

触发器创建:

#新增用户后插入日志
DROP TRIGGER IF EXISTS insert_user_after;
CREATE TRIGGER insert_user_after after INSERT ON user FOR EACH ROW
BEGIN
INSERT INTO user_log(id,user_id,type)VALUES(UUID(),new.id,'insert(after)');
END;
#新增用户前插入日志
DROP TRIGGER  IF EXISTS insert_user_before;
CREATE TRIGGER insert_user_before before INSERT ON user FOR EACH ROW
BEGIN
INSERT INTO user_log(id,user_id,type)VALUES(UUID(),new.id,'insert(before)');
END;
#删除用户前插入日志
DROP TRIGGER  IF EXISTS delete_user_before;
CREATE TRIGGER delete_user_before before delete ON user FOR EACH ROW
BEGIN
INSERT INTO user_log(id,user_id,type)VALUES(UUID(),old.id,'delete(before)');
END;
#删除用户后插入日志
DROP TRIGGER  IF EXISTS delete_user_after;
CREATE TRIGGER delete_user_after after delete ON user FOR EACH ROW
BEGIN
INSERT INTO user_log(id,user_id,type)VALUES(UUID(),old.id,'delete(after)');
END;

#修改用户后插入日志表和日志详情表
DROP TRIGGER  IF EXISTS  update_user_after;
CREATE TRIGGER update_user_after after update ON user FOR EACH ROW
BEGIN
DECLARE name VARCHAR(64) DEFAULT '';
DECLARE phone VARCHAR(64) DEFAULT '';
DECLARE address VARCHAR(64) DEFAULT '';
SET @log_id = UUID();
INSERT INTO user_log(id,user_id,type)VALUES(@log_id,new.id,'update(after)');

IF old.name <> new.name 
    THEN INSERT INTO user_log_detail VALUES(UUID(),@log_id,'用户名称',old.name,new.name);
END IF;
IF old.phone <> new.phone
    THEN INSERT INTO user_log_detail VALUES(UUID(),@log_id,'手机号',old.phone,new.phone);
END IF;
IF old.address <> new.address
    THEN INSERT INTO user_log_detail VALUES(UUID(),@log_id,'用户地址',old.address,new.address);
END IF;
END;

 新增用户:INSERT INTO user(id,name,phone,address) values(UUID(),'test','999999','北京市');

  结果:

                            

 删除用户:INSERT INTO user(id,name,phone,address) values(UUID(),'test','999999','北京市');

结果:

                             

修改用户:update user SET name='test_update',phone='66666_update',address='俺是那嘎达的' where id = '67ef0412-f97f-11ea-8349-00ff5f20e2ec';

结果:

                      

小结:

触发器是对某张table的insert、update、delete操作(事件)进行绑定的一种特殊的存储过程,依靠这些事件的发生去触发执行触发器,触发器实质上是绑定数据表的行,新增的行数据保存在new上、删除的行数据保存在old上、修改的行原始数据保存在old上,新的数据保存在new上。调用:old.col_name   new.col_name

PS:MySQL 的触发器中不能对本表进行 insert、update 和 delete 操作,否则会报错 

测试触发器insert本表:

DROP TRIGGER  IF EXISTS  insert_user_after;
CREATE TRIGGER insert_user_after after INSERT ON user FOR EACH ROW
BEGIN
INSERT INTO user_log(id,user_id,type)VALUES(UUID(),new.id,'insert(after)');
#插入本表user的操作
INSERT INTO user(id,name,phone,address) values(UUID(),'test123','123','123');
END;


##执行insert语句去触发insert_user_after触发器
INSERT INTO user(id,name,phone,address) values(UUID(),'test','999999','北京市');

报错信息:

[SQL]INSERT INTO user(id,name,phone,address) values(UUID(),'test','999999','北京市');
[Err] 1442 - Can't update table 'user' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.

MYSQL事件(定时任务)

在MySQL 5.1中新增了一个特色功能事件调度器(Event Scheduler),简称事件。它可以作为定时任务调度器,取代部分原来只能用操作系统的计划任务才能执行的工作。另外,更值得一提的是,MySQL的事件可以实现每秒钟执行一个任务,这在一些对实时性要求较高的环境下是非常实用的。

事件调度器是定时触发执行的,从这个角度上看也可以称作是“临时触发器”。但是它与触发器又有所区别,触发器只针对某个表产生的事件执行一些语句,而事件调度器则是在某一段(间隔)时间执行一些语句。

查看事件调度器是否开启

事件由一个特定的线程来管理。启用事件调度器后,拥有SUPER权限的账户执行SHOW PROCESSLIST就可以看到这个线程了。

查看事件是否开启

SHOW VARIABLES LIKE 'event_scheduler';
SELECT @@event_scheduler;
SHOW PROCESSLIST;

开启或关闭事件调度器

使用SET GLOBAL命令可以开启或关闭事件。将event_scheduler参数的值设置为ON,则开启事件;如果设置为OFF,则关闭事件。

方法一:

-- 开启事件调度器
SET GLOBAL event_scheduler = ON;
 
-- 关闭事件调度器
SET GLOBAL event_scheduler = OFF;
 
-- 查看事件调度器状态
SHOW VARIABLES LIKE 'event_scheduler';

注意:如果想要始终开启事件,那么在使用SET GLOBAL开启事件后,还需要在my.ini(Windows系统)/my.cnf(Linux系统)中添加event_scheduler=on。因为如果没有添加,MySQL重启事件后又会回到原来的状态。

方法二:通过更改配置文件

在MySQL的配置文件my.ini(Windows系统)/my.cnf(Linux系统)中,找对[mysqld],然后在下面添加以下代码开启事件。

# 事件调度器启动状态
event_scheduler = on

在配置文件中添加代码并保存文件后,还需要重新启动MySQL服务器才能生效。通过该方法开启事件,重启MySQL服务器后,不恢复为系统默认的状态。

创建时间的语法格式

CREATE
	[DEFINER={user | CURRENT_USER}]
	EVENT [IF NOT EXISTS] event_name
	ON SCHEDULE schedule
	[ON COMPLETION [NOT] PRESERVE]
	[ENABLE | DISABLE | DISABLE ON SLAVE]
	[COMMENT 'comment']
	DO event_body;

参数释义:

DEFINER可选,用于定义事件执行时检查权限的用户
IF NOT EXISTS可选项,用于判断要创建的事件是否存在
EVENT event_name必选,用于指定事件名,event_name的最大长度为64个字符,如果为指定event_name,则默认为当前的MySQL用户名(不区分大小写)
ON SCHEDULE schedule必选,用于定义执行的时间和时间间隔
ON COMPLETION [NOT] PRESERVE

可选,用于定义事件是否循环执行,即是一次执行还是永久执行,默认为一次执行,

即 NOT PRESERVE

ENABLE | DISABLE | DISABLE ON SLAVE可选项,用于指定事件的一种属性。其中,关键字ENABLE表示该事件是活动的,也就是调度器检查事件是否必选调用;关键字DISABLE表示该事件是关闭的,也就是事件的声明存储到目录中,但是调度器不会检查它是否应该调用;关键字DISABLE ON SLAVE表示事件在从机中是关闭的。如果不指定这三个选择中的任意一个,则在一个事件创建之后,它立即变为活动的。
COMMENT 'comment'可选,用于定义事件的注释
DO event_body

必选,用于指定事件启动时所要执行的代码。可以是任何有效的SQL语句、存储过程或者一个计划执行的事件。如果包含多条语句,可以使用BEGIN...END复合结构

这个参数要详细了解下

1、ON SCHEDULE  schedule,参数schedule的值为一个AS子句,用于指定事件在某个时刻发生,其语法格式如下:

AT timestamp [+ INTERVAL interval] ...
  | EVERY interval
    [STARTS timestamp [+ INTERVAL interval] ...]
    [ENDS timestamp [+ INTERVAL interval] ...]

参数说明:

(1)timestamp:表示一个具体的时间点,后面加上一个时间间隔,表示在这个时间间隔后事件发生。

(2)EVERY子句:用于表示事件在指定时间区间内每隔多长时间发生一次,其中 STSRTS子句用于指定开始时间;ENDS子句用于指定结束时间。

(3)interval:表示一个从现在开始的时间,其值由一个数值和单位构成。例如,使用“4 WEEK”表示4周;使用“‘1:10’ HOUR_MINUTE”表示1小时10分钟。间隔的距离用DATE_ADD()函数来支配。

interval参数值的语法格式如下:

quantity {YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE |
              WEEK | SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE |
              DAY_SECOND | HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND}

一些常用的时间间隔设置:

(1)每隔5秒钟执行

ON SCHEDULE EVERY 5 SECOND

(2)每隔1分钟执行

ON SCHEDULE EVERY 1 MINUTE

(3)每天凌晨1点执行

ON SCHEDULE EVERY 1 DAY STARTS DATE_ADD(DATE_ADD(CURDATE(), INTERVAL 1 DAY), INTERVAL 1 HOUR)

(4)每个月的第一天凌晨1点执行

ON SCHEDULE EVERY 1 MONTH STARTS DATE_ADD(DATE_ADD(DATE_SUB(CURDATE(),INTERVAL DAY(CURDATE())-1 DAY),INTERVAL 1 MONTH),INTERVAL 1 HOUR)

(5)每 3 个月,从现在起一周后开始

ON SCHEDULE EVERY 3 MONTH STARTS CURRENT_TIMESTAMP + 1 WEEK

(6)每十二个小时,从现在起三十分钟后开始,并于现在起四个星期后结束

ON SCHEDULE EVERY 12 HOUR STARTS CURRENT_TIMESTAMP + INTERVAL 30 MINUTE ENDS CURRENT_TIMESTAMP + INTERVAL 4 WEEK

 (7)在yyyy-MM-dd hh:mm:ss执行

on schedule at '2016-10-01 21:50:00'                  -- 在2016年10月1日,晚上9点50执行

示例1:创建名称为event_user的事件,用于每隔5秒钟向数据表tb_user(用户信息表)中插入一条数据。

(1)首先创建tb_user(用户信息表)。

-- 创建用户信息表

CREATE TABLE IF NOT EXISTS tb_user

(

id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户编号',

name VARCHAR(30) NOT NULL COMMENT '用户姓名',

create_time TIMESTAMP COMMENT '创建时间'

) COMMENT = '用户信息表';

(2)创建事件。

-- 创建事件

CREATE EVENT IF NOT EXISTS event_user

ON SCHEDULE EVERY 5 SECOND

ON COMPLETION PRESERVE

COMMENT '新增用户信息定时任务'

DO INSERT INTO tb_user(name,create_time) VALUES('pan_junbiao的博客',NOW());

执行结果:

示例2:创建一个事件,实现每个月的第一天凌晨1点统计一次已经注册的会员人数,并插入到统计表中。

(1)创建名称为p_total的存储过程,用于统计已经注册的会员人数,并插入到统计表tb_total中。

CREATE PROCEDURE p_total()

BEGIN

DECLARE n_total INT default 0;

SELECT COUNT(*) INTO n_total FROM db_database11.tb_user;

INSERT INTO tb_total (userNumber,createtime) VALUES(n_total,NOW());

END;

(2)创建名称为e_autoTotal的事件,用于在每个月的第一天凌晨1点调用存储过程。

CREATE EVENT IF NOT EXISTS e_autoTotal

ON SCHEDULE EVERY 1 MONTH STARTS DATE_ADD(DATE_ADD(DATE_SUB(CURDATE(),INTERVAL DAY(CURDATE())-1 DAY),INTERVAL 1 MONTH),INTERVAL 1 HOUR)

ON COMPLETION PRESERVE ENABLE

DO CALL p_total();

查询事件

在MySQL中可以通过查询information_schema.events表,查看已创建的事件。其语句如下:

SELECT * FROM information_schema.events; 


修改事件

在MySQL 5.1及以后版本中,事件被创建之后,还可以使用ALTER EVENT语句修改其定义和相关属性。其语法如下:

ALTER

[DEFINER={user | CURRENT_USER}]

EVENT [IF NOT EXISTS] event_name

ON SCHEDULE schedule

[ON COMPLETION [NOT] PRESERVE]

[ENABLE | DISABLE | DISABLE ON SLAVE]

[COMMENT 'comment']

DO event_body;

ALTER EVENT语句与CREATE EVENT语句基本相同。另外ALTER EVENT语句还有一个用法就是让一个事件关闭或再次活动。


启动与关闭事件

另外ALTER EVENT语句还有一个用法就是让一个事件关闭或再次活动。

示例:启动名称为event_user的事件。

ALTER EVENT event_user ENABLE;

示例:关闭名称为event_user的事件。

ALTER EVENT event_user DISABLE;


删除事件

在MySQL 5.1及以后版本中,删除已经创建的事件可以使用DROP EVENT语句来实现。

示例:删除名称为event_user的事件。

DROP EVENT IF EXISTS event_user;

  • 0
    点赞
  • 0
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值