第8步---MySQL的存储过程和触发器
1.存储过程
5开始支持的
sql集,类似Java中的代码中的方法
实现对sql的封装和服用
有输入和输出
可以声明变量
可以实现一下复杂的控制语句
1.1入门案例
基本语法
测试数据
-- 创建表的测试数据
create table dept(deptno int primary key,dname varchar(20),loc varchar(20)
);
insert into dept values(10, '教研部','北京'),
(20, '学工部','上海'),
(30, '销售部','广州'),
(40, '财务部','武汉');create table emp(empno int primary key,ename varchar(20),job varchar(20),mgr int,hiredate date,sal numeric(8,2),comm numeric(8, 2),deptno int,
-- FOREIGN KEY (mgr) REFERENCES emp(empno),FOREIGN KEY (deptno) REFERENCES dept(deptno) ON DELETE SET NULL ON UPDATE CASCADE
);
insert into emp values
(1001, '甘宁', '文员', 1013, '2000-12-17', 8000.00, null, 20),
(1002, '黛绮丝', '销售员', 1006, '2001-02-20', 16000.00, 3000.00, 30),
(1003, '殷天正', '销售员', 1006, '2001-02-22', 12500.00, 5000.00, 30),
(1004, '刘备', '经理', 1009, '2001-4-02', 29750.00, null, 20),
(1005, '谢逊', '销售员', 1006, '2001-9-28', 12500.00, 14000.00, 30),
(1006, '关羽', '经理', 1009, '2001-05-01', 28500.00, null, 30),
(1007, '张飞', '经理', 1009, '2001-09-01', 24500.00, null, 10),
(1008, '诸葛亮', '分析师', 1004, '2007-04-19', 30000.00, null, 20),
(1009, '曾阿牛', '董事长', null, '2001-11-17', 50000.00, null, 10),
(1010, '韦一笑', '销售员', 1006, '2001-09-08', 15000.00, 0.00, 30),
(1011, '周泰', '文员', 1008, '2007-05-23', 11000.00, null, 20),
(1012, '程普', '文员', 1006, '2001-12-03', 9500.00, null, 30),
(1013, '庞统', '分析师', 1004, '2001-12-03', 30000.00, null, 20),
(1014, '黄盖', '文员', 1007, '2002-01-23', 13000.00, null, 10);create table salgrade(grade int primary key,losal int,hisal int
);
insert into salgrade values
(1, 7000, 12000),
(2, 12010, 14000),
(3, 14010, 20000),
(4, 20010, 30000),
(5, 30010, 99990);
存储过程创建
-- =============================存储过程==================
-- 创建存储过程
delimiter $$
CREATE PROCEDURE proc01()
BEGIN
SELECT empno,ename FROM emp;
END $$
delimiter ;-- 调用存储过程
call proc01();
1.2局部变量
用户自定在beigin和end中是有效的
基本语法
DECLARE 变量名称 数据类型 DEFAULT '默认值';
DECLARE var_name01 VARCHAR(20) DEFAULT '默认值';
set方式赋值
-- 用户变量
delimiter $$
CREATE PROCEDURE proc02()
BEGIN
DECLARE var_name01 VARCHAR(20) DEFAULT '默认值';
SET var_name01='张三';
SELECT var_name01;
END $$
delimiter ;-- 调用存储过程
call proc02();
insert into的方式赋值
-- 采用insert into的方式赋值
delimiter $$
CREATE PROCEDURE proc03()
BEGIN
DECLARE var_name01 VARCHAR(20) DEFAULT '默认值';
SELECT ename into var_name01 FROM emp WHERE empno='1001';
SELECT var_name01;END $$
delimiter ;-- 调用存储过程
call proc03();
注意只能是一行的数据,要是多行的话就会出现错误了,要是那样的话就会出现错误.
1.3会话变量
当连接的时候一直有效的变量
-- 会话变量
DROP PROCEDURE proc05;
delimiter $$
CREATE PROCEDURE proc05()
BEGIN
set @var_name='张伞';
SELECT @var_name;
END $$
delimiter ;-- 调用存储过程
call proc05();
SELECT @var_name;
会话变量不仅可以在函数中可以进行使用,也是可以在函数外边进行使用的.
1.4系统变量
my.ini中可以存储一些系统变化.
会话变量是建立连接的时候初始化的.
所有的系统变量都会有一份会话变量的拷贝的变量.
全局会影响到整个mysql,但是会话的话只能影响到当前的会话
系统变量
-- 查看全部的全局变量
SHOW GLOBAL VARIABLES;-- 查看某一个
SELECT @@global.auto_increment_increment ;-- 修改全局变量的值
SELECT @@global.sort_buffer_size ;set GLOBAL sort_buffer_size =262144;
set @@GLOBAL sort_buffer_size =262144;
会话变量
只需要将global改成session就可以了
-- 查看全部的会话变量
SHOW SESSION VARIABLES;-- 查看某一个
SELECT @@SESSION.auto_increment_increment ;-- 修改会话变量的值
SELECT @@SESSION.sort_buffer_size ;set SESSION sort_buffer_size =262145;
1.5in关键字
此处的in关键字是输入参数的含义不是子查询的关键字
可以给存储过程的方法进行传递参数
仅仅在函数的内部进行使用的
-- 传入员工编号查看员工的信息
DROP PROCEDURE IF EXISTS proc07;
delimiter $$
CREATE PROCEDURE proc07(IN param_eno INT)
BEGINSELECT * FROM emp WHERE empno=param_eno;END $$
delimiter ;CALL proc07(1002);
-- 传入部门名和薪资,查询指定部门并且薪资大于指定值的员工信息
DROP PROCEDURE IF EXISTS proc08;
delimiter $$
CREATE PROCEDURE proc08 (IN param_dname VARCHAR(20),IN param_salary DOUBLE)-- 传入部门名喝薪资,查询指定部门并且薪资大于指定值的员工信息(IN param_eno INT)
BEGINSELECT * FROM dept d JOIN emp e ON d.deptno=e.deptno and d.dname= param_dname and e.sal>param_salary ;END $$
delimiter ;CALL proc08('销售部',20000);
1.6out关键字
将里面值传出去
-- ======OUT关键字
-- 传入员工的编号返回员工的名字
DROP PROCEDURE IF EXISTS proc09;
delimiter $$
CREATE PROCEDURE proc09 (IN param_eno INT ,OUT out_name VARCHAR(20))
-- 传入部门名喝薪资,查询指定部门并且薪资大于指定值的员工信息(IN param_eno INT)
BEGINSELECT ename into out_name FROM emp WHERE empno =param_eno ;
END $$
delimiter ;CALL proc09(1002,@o_name);SELECT @o_name;
再查看一下员工的薪资
DROP PROCEDURE IF EXISTS proc09;
delimiter $$
CREATE PROCEDURE proc09 (IN param_eno INT ,OUT out_name VARCHAR(20),OUT out_salary DECIMAL(7,2))
-- 传入部门名喝薪资,查询指定部门并且薪资大于指定值的员工信息(IN param_eno INT)
BEGIN
SELECT ename ,sal INTO out_name ,out_salary FROM emp WHERE empno =param_eno ;
END $$
delimiter ;CALL proc09(1002,@o_name,@o_salary);SELECT @o_name,@o_salary;
1.7inout关键字
可以传入也可以传出
-- 以10倍的输出
DROP PROCEDURE IF EXISTS proc10;
delimiter $$
CREATE PROCEDURE proc10 (INOUT param_eno INT )
BEGIN
SET param_eno=param_eno*10;
END $$
delimiter ;SET @nox=5;
CALL proc10(@nox);SELECT @nox;
-- 传入员工名字,拼接部门号,传入薪资,求出年薪
DROP PROCEDURE IF EXISTS proc11;
delimiter $$
CREATE PROCEDURE proc11 (INOUT in_ename VARCHAR(20) ,INOUT sal DOUBLE )
BEGIN
SELECT CONCAT_WS('-',deptno,ename) INTO in_ename FROM emp WHERE emp.ename=in_ename;
SET sal=sal*12;
END $$
delimiter ;set @e='周瑜',@s=1000;CALL proc11(@e,@s);SELECT @e,@s;
1.8存储过程流程控制语句
-- 传入员工名字,拼接部门号,传入薪资,求出年薪
DROP PROCEDURE IF EXISTS proc11;
delimiter $$
CREATE PROCEDURE proc11 (INOUT in_ename VARCHAR(20) ,INOUT sal DOUBLE )
BEGIN
SELECT CONCAT_WS('-',deptno,ename) INTO in_ename FROM emp WHERE emp.ename=in_ename;
SET sal=sal*12;
END $$
delimiter ;set @e='周瑜',@s=1000;CALL proc11(@e,@s);SELECT @e,@s;-- 流程判断
-- 利用流程控制语句
DROP PROCEDURE IF EXISTS proc11;
delimiter $$
CREATE PROCEDURE proc11(IN socre INT)
BEGIN
IF socre<60 THENSELECT '不及格';
ELSEIF socre>=60 AND socre <80 THEN
SELECT '及格';ELSEIF socre>=80 AND socre <=100 THENSELECT '优秀';
ELSESELECT '天才';
END IF;
END $$
delimiter ;CALL proc11(75);-- 输入员工的名字,判断工资的情况
DROP PROCEDURE IF EXISTS proc12;
delimiter $$
CREATE PROCEDURE proc12(IN in_ename VARCHAR(20))
BEGIN
DECLARE in_salary DECIMAL(7,2); -- 定义一个薪资
SELECT sal into in_salary FROM emp WHERE ename=in_ename; -- 将指定员工的薪资查询出来
-- 下面进行薪资的判断
IF in_salary<10000 THENSELECT '1万元以下';
ELSEIF in_salary<20000 THENSELECT '2万元以下,1万元以上';
ELSESELECT '2万元以上';
END IF;END $$
delimiter ;CALL proc12('张飞');CALL proc12('甘宁');
1.9分支语句
case在存储过程中的使用
和if差不多,类似Java中的switch。
语法格式有两种
其中一种是数值的匹配,还有一种是单纯的进行表达式的判断的结构的。
-- =======case分支语句============================
DROP PROCEDURE IF EXISTS proc13;
delimiter $$
CREATE PROCEDURE proc13(IN pay_type int)
BEGIN
CASE pay_typeWHEN 1 THENSELECT '支付宝';WHEN 2 THENSELECT '微信支付';ELSESELECT '未知支付方式';
END CASE;
END $$
delimiter ;call proc13(1);
采用第二种语法格式实现的
DROP PROCEDURE IF EXISTS proc14;
delimiter $$
CREATE PROCEDURE proc14(IN pay_type int)
BEGIN
CASE WHEN pay_type=1 THENSELECT '支付宝';WHEN pay_type=2 THENSELECT '微信支付';ELSESELECT '未知支付方式';
END CASE;
END $$
delimiter ;call proc14(1);
1.10循环语句
MySQL中的循环的方式
MySQL中的循环的方式
while
repeat
loop
循环跳出
leave 类似break,跳出当前的循环
iterate 和continue,结束本次循环
1.11while循环
语法格式
创建下面的表
CREATE TABLE `t_user` (`uid` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,`password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
循环插入line条数据
-- =================================循环语句============================
-- 循环插入line条数据
DROP PROCEDURE IF EXISTS proc15;
delimiter $$
CREATE PROCEDURE proc15( IN line INT )
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i<=line DOINSERT INTO t_user(name,password) VALUES (CONCAT('张',i),'123456');SET i=i+1;
END WHILE;
END $$
delimiter ;call proc15(5);
加入对应的循环控制的操作
-- 加上基本的流程控制语句DROP PROCEDURE IF EXISTS proc15;
delimiter $$
CREATE PROCEDURE proc15( IN line INT )
BEGIN
DECLARE i INT DEFAULT 1;
label:WHILE i<=line DOINSERT INTO t_user(name,password) VALUES (CONCAT('张',i),'123456');IF i=5 THENLEAVE label;end IF;SET i=i+1;
END WHILE label;
END $$
delimiter ;call proc15(5);
要是解决这个死循环的操作的话可以把+1的操作放在前面进行操作。这样就不吹出现死循环的操作了。
1.12repeat循环操作
-- repeat的操作
DROP PROCEDURE IF EXISTS proc16;
delimiter $$
CREATE PROCEDURE proc16( IN line INT )
BEGIN
DECLARE i INT DEFAULT 10;
label:REPEATINSERT INTO t_user(name,password) VALUES (CONCAT('张',i),'123456');SET i=i+1;UNTIL i>=line -- 跳出循环的操作
END REPEAT label;END $$
delimiter ;call proc16(15);
上面的操作会插入5条数据。
1.13loop循环
-- loop操作
DROP PROCEDURE IF EXISTS proc17;
delimiter $$
CREATE PROCEDURE proc17( IN line INT )
BEGIN
DECLARE i INT DEFAULT 1;
label:LOOPINSERT INTO t_user(name,password) VALUES (CONCAT('赵',i),'123456');SET i=i+1;if i>lineTHEN LEAVE label;END IF;-- 跳出循环的操作
END LOOP label;END $$
delimiter ;call proc17(5);
1.14游标
存储查询结果,对查询的结果进行额外的处理的,是相当于多行的数据的,不是单行的数据的。
下面不是完整的操作
-
下面的操作只能获取一行的数据
OPEN my_cursor;-- 获取数据 执行一次获取一行的数据
FETCH my_cursor into var_empno,var_empname,var_sal;
SELECT var_empno,var_empname,var_sal;-- 关闭游标
CLOSE my_cursor;
可以采用循环的操作实现游标数据的读取的操作
-- ===================游标操作====================
-- 输入部门名查询这个部门的员工的详细的信息
DROP PROCEDURE IF EXISTS proc18;
delimiter $$
CREATE PROCEDURE proc18( IN in_name VARCHAR(50) )
BEGIN-- 局部变量
DECLARE var_empno int;
DECLARE var_empname VARCHAR(50);
DECLARE var_sal DECIMAL(7,2);-- 声明游标
DECLARE my_cursor CURSOR FOR
SELECT empno,ename,sal
FROM emp e JOIN dept d ON d.deptno=e.deptno and d.dname=in_name;-- 打开游标
OPEN my_cursor;
label:LOOP-- 获取数据 执行一次获取一行的数据
FETCH my_cursor into var_empno,var_empname,var_sal;
SELECT var_empno,var_empname,var_sal;END LOOP label;-- 关闭游标
CLOSE my_cursor;END $$ ;
delimiter ;call proc18('销售部');
上面的代码是存在问题的,虽然可以实现展示全部的数据但是展示的全部的数据是有问题的,有些数据并不是我们想要展示的。存在错误。
1.15句柄操作
解决读取到最后一行数据的操作
其实就是类似一种异常操作的处理
有三种执行的方式
继续执行
停止执行
不执行
出现错误有错误码。错误的名字等信息。
-- ===================游标操作====================
-- 输入部门名查询这个部门的员工的详细的信息
DROP PROCEDURE IF EXISTS proc18;
delimiter $$
CREATE PROCEDURE proc18( IN in_name VARCHAR(50) )
BEGIN-- 局部变量
DECLARE var_empno int;
DECLARE var_empname VARCHAR(50);
DECLARE var_sal DECIMAL(7,2);-- 声明游标
DECLARE my_cursor CURSOR FOR
SELECT empno,ename,sal
FROM emp e JOIN dept d ON d.deptno=e.deptno and d.dname=in_name;-- 打开游标
OPEN my_cursor;
label:LOOP-- 获取数据 执行一次获取一行的数据
FETCH my_cursor into var_empno,var_empname,var_sal;
SELECT var_empno,var_empname,var_sal;END LOOP label;-- 关闭游标
CLOSE my_cursor;END $$ ;
delimiter ;call proc18('销售部');-- 句柄操作
DROP PROCEDURE IF EXISTS proc18;
delimiter $$
CREATE PROCEDURE proc18( IN in_name VARCHAR(50) )
BEGIN-- 局部变量
DECLARE var_empno int;
DECLARE var_empname VARCHAR(50);
DECLARE var_sal DECIMAL(7,2);
DECLARE flag INT DEFAULT 1; -- 定义一个标志-- 声明游标
DECLARE my_cursor CURSOR FOR
SELECT empno,ename,sal
FROM emp e JOIN dept d ON d.deptno=e.deptno and d.dname=in_name;-- 定义异常的处理的方式
-- 出现问题了该怎么做,是继续进行执行还是终止exit,还是undo不支持
-- 触发的条件
-- 异常出现之后执行什么东西 设置flag的值 到时候不进行执行DECLARE CONTINUE HANDLER FOR 1329 SET flag=0;-- 打开游标
OPEN my_cursor;
label:LOOP-- 获取数据 执行一次获取一行的数据
FETCH my_cursor into var_empno,var_empname,var_sal;-- 判断flag 是0的话不进行执行
IF flag =1 THENSELECT var_empno,var_empname,var_sal;ELSELEAVE label;END IF;END LOOP label;-- 关闭游标
CLOSE my_cursor;END $$ ;
delimiter ;call proc18('销售部');
1329是一个错误代码找不到的错误的代码。当找不到数据的时候就进行捕获了,走我们原先定义号的操作。
1.16总结
特点:
-
代码复用
-
比正常写代码的时候是快的
变量的范围有好几种
-
局部:select into
-
全局:@@变量名称
-
用户:@变量名称
会话变量直接进行select就可以创建。
系统变量可以设置在本地的my.ini文件中。
分支语句
-
两种语法格式
循环
-
while
-
repeat+until
-
loop:本身是一个死循环,需要借助if设置条件
1.17创建存储函数
需要先执行set GLOBAL log_bin_trust_function_creators=true;
不然会出现安全认证的错误不能成功的执行我们的函数不然话没法执行成功的。
-- 允许创建函数权限信任
set GLOBAL log_bin_trust_function_creators=true;-- 创建存储函数
DROP FUNCTION IF EXISTS test;delimiter $$
CREATE FUNCTION test() RETURNS int
BEGINDECLARE cnt INT DEFAULT 0;SELECT count(*) INTO cnt FROM emp;
RETURN cnt;
END $$delimiter ;SELECT test();
下面执行实际的表的操作
-- 传入员工的名称返回员工的名字
DROP FUNCTION IF EXISTS test;delimiter $$
CREATE FUNCTION test(in_no int ) RETURNS VARCHAR(50)
BEGINDECLARE out_name VARCHAR(50) ;SELECT ename INTO out_name FROM emp WHERE empno=in_no;
RETURN out_name;
END $$delimiter ;SELECT test('1001');
和存储过程的操作是类似的,但是是基于select查询的操作进行执行的不是call执行的。
2.触发器
实际上是表之间关系的一种操作
是自动执行的操作,只有增删改查的操作才会记录对应的日志的信息。
只能支持行的数据触发,只有行中的数据变化才能触发触发器
2.1基本操作
什么条件触发
什么时候触发
触发频率
定义在表上还是定义附属在表 上
-- ====================触发器===================================
-- 创建触发器CREATE TRIGGER 触发器名称 BEFORE |AFTER 触发事件
ON 表名 for each ROW
BEGIN
执行语句列表
END
创建测试的表
CREATE TABLE `s_user` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,`password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;CREATE TABLE `user_log` (`id` int(11) NOT NULL AUTO_INCREMENT,`time` datetime DEFAULT NULL,`log_text` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;-- 创建好两张测试的表
DROP TRIGGER IF EXISTS tri_1;
CREATE TRIGGER tri_1
AFTER INSERT
ON s_user
FOR EACH ROW
BEGIN
INSERT INTO user_log VALUES(NULL,NOW(),'添加新用户') ;
END;INSERT INTO `pz`.`s_user` (`id`, `name`, `password`) VALUES (1, '张伞', '123456');
-- 当数据被修改的时候也是会记录对应的修改的信息
DROP TRIGGER IF EXISTS tri_2;
CREATE TRIGGER tri_2
BEFORE UPDATE
ON s_user
FOR EACH ROW
BEGIN
INSERT INTO user_log VALUES(NULL,NOW(),'有用户被修改') ;
END;UPDATE `pz`.`s_user` SET `name` = '李四', `password` = '123456' WHERE `id` = 1;
2.2NEW和OLD
是触发器的两个关键字记录的是修改前和修改后的用户的数据的信息
-
插入的时候只能获取new的数据
-
修改可以获取old和new的数据
-
delete可以获取old的数据
获取的时候直接
-
old.列名
-
new.列名
-- 记录修改前和修改后的数据
DROP TRIGGER IF EXISTS tri_1;
CREATE TRIGGER tri_1
AFTER INSERT
ON s_user
FOR EACH ROW
BEGIN
INSERT INTO user_log VALUES(NULL,NOW(),CONCAT_WS('-','添加新用户','用户名',new.name,'密码',new.password)) ;
END;INSERT INTO `pz`.`s_user` (`id`, `name`, `password`) VALUES (NULL, '张伞', '123456');
-- 记录修改前和修改后的数据
-- 自增的数据也是可以获取的
DROP TRIGGER IF EXISTS tri_1;
CREATE TRIGGER tri_1
AFTER INSERT
ON s_user
FOR EACH ROW
BEGIN
INSERT INTO user_log VALUES(NULL,NOW(),CONCAT_WS('-','添加新用户','账号',new.id,'用户名',new.name,'密码',new.password)) ;
END;INSERT INTO `pz`.`s_user` (`id`, `name`, `password`) VALUES (NULL, '张伞', '123456');
-- 修改后的数据DROP TRIGGER IF EXISTS tri_1;
CREATE TRIGGER tri_1
BEFORE UPDATE
ON s_user
FOR EACH ROW
BEGIN
INSERT INTO user_log VALUES(NULL,NOW(),CONCAT_WS('-','修改前用户','账号',old.id,'用户名',old.name,'密码',old.password)) ;
INSERT INTO user_log VALUES(NULL,NOW(),CONCAT_WS('-','修改后用户','账号',new.id,'用户名',new.name,'密码',new.password)) ;
END;UPDATE `pz`.`s_user` SET `name` = '李四修改', `password` = '12345678' WHERE `id` = 1;
DROP TRIGGER IF EXISTS tri_1;
CREATE TRIGGER tri_1
AFTER DELETE
ON s_user
FOR EACH ROW
BEGIN
INSERT INTO user_log VALUES(NULL,NOW(),CONCAT_WS('-','删除前用户','账号',old.id,'用户名',old.name,'密码',old.password)) ;
END;DELETE FROM s_user WHERE `id` = 1;
2.3其他操作
-- 查看触发器
SHOW TRIGGERS;-- 删除触发器
DROP TRIGGER IF EXISTS tri_1;
2.4注意事项
不能对本表的数据进行增删改的操作,避免递归
消耗资源
可以转换成后台的代码,转化成代码的操作
维护是困难的。