MySQL基础(六)流程控制、游标、触发器、窗口函数

目录

定义条件与处理程序

定义条件

定义处理程序

流程控制

IF

分支结构之 CASE

 循环结构之LOOP

 循环结构之WHILE

循环结构之REPEAT

跳转语句leave和iterate 

游标

使用游标步骤

 全局变量的持久化

触发器

触发器的使用

查看触发器

删除触发器


定义条件与处理程序

定义条件 是事先定义程序执行过程中可能遇到的问题, 处理程序 定义了在遇到问题时应当采取的处理方 式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能 力,避免程序异常停止运行。

说明:定义条件和处理程序在存储过程、存储函数中都是支持的。

定义条件

定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个 错误名字 和 指定的 错误条件 关联起来。这个名字可以随后被用在定义处理程序的 DECLARE HANDLER 语句中。

DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)

错误码的说明: MySQL_error_code 和 sqlstate_value 都可以表示MySQL的错误。 MySQL_error_code是数值类型错误代码。

sqlstate_value是长度为5的字符串类型错误代码。 

举例

#使用MySQL_error_code
DECLARE Field_Not_Be_NULL CONDITION FOR 1048;
#使用sqlstate_value
DECLARE Field_Not_Be_NULL CONDITION FOR SQLSTATE '23000';

定义处理程序

可以为SQL执行过程中发生的某种类型的错误定义特殊的处理程序。定义处理程序时,使用DECLARE语句 的语法如下:

DECLARE 处理方式 HANDLER FOR 错误类型 处理语句

处理方式:处理方式有3个取值:CONTINUE、EXIT、UNDO。

CONTINUE :表示遇到错误不处理,继续执行。

EXIT :表示遇到错误马上退出。

UNDO :表示遇到错误后撤回之前的操作。MySQL中暂时不支持这样的操作。

错误类型(即条件)可以有如下取值:

SQLSTATE '字符串错误码' :表示长度为5的sqlstate_value类型的错误代码;

MySQL_error_code :匹配数值类型错误代码;

错误名称 :表示DECLARE ... CONDITION定义的错误条件名称。

SQLWARNING :匹配所有以01开头的SQLSTATE错误代码;

NOT FOUND :匹配所有以02开头的SQLSTATE错误代码;

SQLEXCEPTION :匹配所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE错误代码;

处理语句:如果出现上述条件之一,则采用对应的处理方式,并执行指定的处理语句。语句可以是 像“ SET 变量 = 值 ”这样的简单语句,也可以是使用 BEGIN ... END 编写的复合语句。 

举例

delimiter //
create procedure test_fault()
begin
declare continue handler for 1048 set @y=1;
set @x=1;
update employees set email = null where employee_id=101;
set @x=2;
end //

此时y=1,x=2

流程控制

IF

IF 表达式1 THEN 操作1
[ELSEIF 表达式2 THEN 操作2]……
[ELSE 操作N]
END IF

举例 

delimiter //
create procedure IF_TEST()
begin
declare age int default 20;
if age>40
	then select '111';
elseif age>18
	then select '222';
else
	select '333';
end if;
end //

分支结构之 CASE

#情况一:类似于switch
CASE 表达式
WHEN 值1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 值2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)

#情况二:类似于多重if
CASE
WHEN 条件1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 条件2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)

举例

delimiter //
create procedure IF_TEST()
begin
declare a int default 1;
case a
when 0 then select '1111';
when 1 then select '22222';
else select '33333';
end case;
end //

delimiter //
create procedure IF_TEST()
begin
declare a int default 1;
case 
when a = 0 then select '1111';
when a = 1 then select '22222';
else select '33333';
end case;
end //

 循环结构之LOOP

LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE子 句),跳出循环过程。 LOOP语句的基本格式如下:

[loop_label:] LOOP
循环执行的语句
IF XXX
THEN LEAVE loop_label;
END IF;
END LOOP [loop_label]

 loop_label 好像是必须要写,不然无法退出循环

delimiter //
create procedure IF_TEST()
begin
declare a int default 1;
loop_label: LOOP
		set a = a+1;
		if a >= 10
			then leave loop_label;
		end if;
	end LOOP loop_label;
	select a;
end //
delimiter //
create procedure IF_TEST(out num int)
begin
declare a double default 0;
set num = 0;
loop_label: LOOP
	select avg(salary) into a from employees;
	if a >=12000
		then leave loop_label;
	end if;
	update employees set salary = salary*1.1;
	set num = num+1;
	end LOOP loop_label;
end //

 循环结构之WHILE

WHILE语句创建一个带条件判断的循环过程。WHILE在执行语句执行时,先对指定的表达式进行判断,如 果为真,就执行循环内的语句,否则退出循环。WHILE语句的基本格式如下:

[while_label:] WHILE 循环条件 DO
循环体
END WHILE [while_label];
delimiter //
create procedure IF_TEST()
begin
declare a int default 0;
while a<=10 do
    set a=a+1;
end while;
select a;
end //

循环结构之REPEAT

REPEAT语句创建一个带条件判断的循环过程。与WHILE循环不同的是,REPEAT 循环首先会执行一次循 环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会 就继续执行循环,直到满足退出条件为止。

REPEAT语句的基本格式如下:

[repeat_label:] REPEAT
循环体的语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]

注意 until xxx后面不需要加;

delimiter //
create procedure IF_TEST()
begin
	declare a int default 0;
	repeat
		set a=a+1;
		until a>=10
		end repeat;
	select a;
end //

 对比三种循环结构:

1、这三种循环都可以省略名称,但如果循环中添加了循环控制语句(LEAVE或ITERATE)则必须添加名 称。

2、 LOOP:一般用于实现简单的"死"循环 WHILE:先判断后执行 REPEAT:先执行后判

断,无条件 至少执行一次

跳转语句leave和iterate 

leave可以理解为break 

LEAVE 标记名

leave不仅仅可以用在循环体中,也可以用在普通的 begin_end中

DELIMITER //
CREATE PROCEDURE leave_begin(IN num INT)
begin_label: BEGIN
    IF num<=0
        THEN LEAVE begin_label;
    ELSEIF num=1
        THEN SELECT AVG(salary) FROM employees;
    ELSEIF num=2
        THEN SELECT MIN(salary) FROM employees;
    ELSE
        SELECT MAX(salary) FROM employees;
    END IF;
SELECT COUNT(*) FROM employees;
END //

ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序 转到语句段开头处。如果你有面向过程的编程语言的使用经验,你可以把 ITERATE 理解为 continue,意思为“再次循环”。

delimiter //
create procedure IF_TEST()
begin
	declare a int default 0;
	my_loop:LOOP
	SET a = a+1;
		if a<10
			then iterate my_loop;
		elseif a>15
			then leave my_loop;
		end if;
		
	end loop;
	select a;
end //

游标

虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录, 但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是 随意定位到某一 条记录 ,并对记录的数据进行处理。

这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录 进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过 程开发的能力。

打个比方,如果说我的需求是,工资大于10000的人涨薪1000,其余的人涨薪2000。那么我们必须一个一个定位到每一个员工,这样的操作可以使用游标实现

使用游标步骤

1、 声明游标

DECLARE cursor_name CURSOR FOR select_statement;

2、打开游标

OPEN cursor_name

3、使用游标(从游标中取得数据)

FETCH cursor_name INTO var_name [, var_name] ...

4、关闭游标

CLOSE cursor_name

举个例子

实现循环查看员工的工资,大于10000的涨薪1000,其余的涨薪2000

delimiter //
create procedure TEST()
begin
	declare emp_sal double(8,2) default 0;
	declare flag int default 0;
	declare sal_cursor cursor for select salary from employees;
	
	declare continue handler for NOT FOUND set flag=1;
	open sal_cursor;
	loop_label:LOOP
		if flag>=1
			then leave loop_label;
		end if;
		fetch sal_cursor into emp_sal;
		if emp_sal>10000
			THEN UPDATE employees set salary = salary+1000 where salary = emp_sal;
		else
			UPDATE employees set salary = salary+2000 where salary = emp_sal;
		end if;
	end loop;
	close sal_cursor;
end //

declare continue handler for NOT FOUND set flag=1;

这句话保证了循环完所有数据后能退出循环

游标是 MySQL 的一个重要的功能,为 逐条读取 结果集中的数据,提供了完美的解决方案。跟在应用层 面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。

但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行 加锁 ,这样在业务并发量大 的时候,不仅会影响业务之间的效率,还会 消耗系统资源 ,造成内存不足,这是因为游标是在内存中进 行的处理。

建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。

 全局变量的持久化

之前在设置全局变量的时候,如果重启了sql就会丢失这样的设置

使用SET GLOBAL语句设置的变量值只会 临时生效 。 数据库重启 后,服务器又会从MySQL配置文件中读取 变量的默认值。 MySQL 8.0版本新增了 SET PERSIST 命令。例如,设置服务器的最大连接数为1000:

SET PERSIST global max_connections = 1000;

触发器

触发器是由 事件来触发 某个操作,这些事件包括 INSERT 、 UPDATE 、 DELETE 事件。所谓事件就是指 用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生 了,就会 自动 激发触发器执行相应的操作。

当对数据表中的数据执行插入、更新和删除操作,需要自动执行一些数据库逻辑时,可以使用触发器来 实现。

说到底就是一个监听器,并作出回应。

触发器的使用

CREATE TRIGGER 触发器名称
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
FOR EACH ROW
触发器执行的语句块;

案例1,在test1表插入数据之前,在test2表中插入1

create table test1(
id int primary key auto_increment,
a int
)

CREATE TABLE test2(
id INT PRIMARY KEY AUTO_INCREMENT,
b INT
)

DELIMITER //
create trigger insert_tri
before insert on test1
for each row
begin
	insert into test2(b) values(1);
end //

insert into test1(a) values(222)

select * from test2

案例2,在员工表插入前检查工资是否大于老板的工资,如果是,报错

DELIMITER //
create trigger insert_tri2
before insert on employees
for each row
begin
	declare sal double;
	
	select salary into sal from employees where employee_id = NEW.manager_id;
	if NEW.salary > sal then
		SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误';
		end if;
	
end //

这里使用了 SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误'; 手动触发报错,且使用NEW.获取新加入的数据

查看触发器

 查看触发器是查看数据库中已经存在的触发器的定义、状态和语法信息等。

方式1:查看当前数据库的所有触发器的定义

SHOW TRIGGERS

方式2:查看当前数据库中某个触发器的定义

SHOW CREATE TRIGGER 触发器名

方式3:从系统库information_schema的TRIGGERS表中查询“salary_check_trigger”触发器的信息。

SELECT * FROM information_schema.TRIGGERS

删除触发器

DROP TRIGGER IF EXISTS 触发器名称

新特性1、窗口函数

  对于数据分组处理,我们之前学过了  group by,但是这样会造成一个类别的所有数据都被压缩到一条数据,而无法处理分类别的单个数据处理

举个例子,对于不同种类的商品,我想统计每个商品与同类商品中最贵的商品的差价,那么可能需要根据不同的类别对数据进行单独处理,窗口函数就可以实现。

都是分类,窗口函数分类完,数据条数不变

窗口函数分类

窗口函数可以分为 静态窗口函数 动态窗口函数 。

静态窗口函数的窗口大小是固定的,不会因为记录的不同而不同;

动态窗口函数的窗口大小会随着记录的不同而变化。

MySQL基础(六)流程控制、游标、触发器、窗口函数_第1张图片

语法结构

函数 OVER([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
or
函数 OVER 窗口名 … WINDOW 窗口名 AS ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])

 下面的案例针对这个表进行MySQL基础(六)流程控制、游标、触发器、窗口函数_第2张图片

序号函数

ROW_NUMBER()函数

ROW_NUMBER()函数能够对数据中的序号进行顺序显示。

举例:查询 goods 数据表中每个商品分类下价格降序排列的各个商品信息。

SELECT ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY price DESC) AS
row_num,id, category_id, category, NAME, price, stock
FROM goods;

MySQL基础(六)流程控制、游标、触发器、窗口函数_第3张图片

 这里主要生成了一个row_num列,用于记录每个类别下的排序序号

RANK()函数

SELECT RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS
row_num,id, category_id, category, NAME, price, stock
FROM goods;

MySQL基础(六)流程控制、游标、触发器、窗口函数_第4张图片

跟上面那个的主要区别是,当遇到price一样的商品,rank函数给予的序号是一样的

 DENSE_RANK()函数

SELECT DENSE_RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS
row_num,id, category_id, category, NAME, price, stock
FROM goods;

MySQL基础(六)流程控制、游标、触发器、窗口函数_第5张图片

 主要区别是,两个89.9元的都序号是2,后面那个序号是3而不是4

分布函数

PERCENT_RANK()函数

PERCENT_RANK()函数是等级值百分比函数。按照如下方式进行计算。

 其中,rank的值为使用RANK()函数产生的序号,rows的值为当前窗口的总记录数。

SELECT PERCENT_RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS pr,
id, category_id, category, NAME, price, stock
FROM goods

 其实就是统计排序在同组中的百分比MySQL基础(六)流程控制、游标、触发器、窗口函数_第6张图片

 CUME_DIST()函数

查询goods数据表中小于或等于当前价格的比例。

SELECT CUME_DIST() OVER(PARTITION BY category_id ORDER BY price ASC) AS cd,
id, category, NAME, price
FROM goods;

grant all privileges on *.* to root@"%" identified by '123456' with grant option;

你可能感兴趣的:(MySQL,mysql,数据库)