存储过程可称为过程化SQL语言
,是在普通SQL语句的基础上增加了编程语言
的特点,把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中,通过逻辑判断、循环等操作实现复杂计算的程序语言。- 换句话说,存储过程其实就是数据库内置的一种编程语言,这种编程语言也有自己的变量、if语句、循环语句等。在一个存储过程中可以将多条SQL语句以逻辑代码的方式将其串联起来,执行这个存储过程就是将这些SQL语句按照一定的逻辑去执行,所以一个存储过程也可以看做是
一组为了完成特定功能的SQL 语句集
。- 每一个存储过程都是一个
数据库对象
,就像table和view一样,存储在数据库当中,一次编译永久有效
。并且每一个存储过程都有自己的名字。客户端程序(Java程序)通过存储过程的名字来调用存储过程。在数据量特别庞大的情况下利用存储过程能达到倍速的效率提升
优点:速度快。
缺点:移植性差。编写难度大。维护性差。
在实际开发中,存储过程还是很少使用的。只有在系统遇到了性能瓶颈,在进行优化的时候,对于大数量的应用来说,可以考虑使用一些。
create procedure p1() begin select empno,ename from emp; end;
call p1();
查看创建存储过程的语句:
show create procedure p1;
通过系统表
information_schema.ROUTINES
查看存储过程的详细信:information_schema.ROUTINES 是 MySQL 数据库中一个系统表
,存储了所有存储过程、函数、触发器的详细信息,包括名称、返回值类型、参数、创建时间、修改时间等。
select * from information_schema.routines where routine_name = 'p1';
information_schema.ROUTINES 表中的一些重要的列包括:
drop procedure if exists p1;
mysql中的变量包括:
系统变量
、用户变量
、局部变量
。
- MySQL 系统变量是指在 MySQL 服务器运行时控制其行为的参数。这些变量可以被设置为特定的值来改变服务器的默认设置,以满足不同的需求。
- MySQL 系统变量可以具有
全局(global)
或会话(session)
作用域。
所有
连接和所有
数据库都适用;当前
连接和当前
数据库适用。查看系统变量
show [global|session] variables; show [global|session] variables like ''; select @@[global|session.]系统变量名;
注意:没有指定session或global时,默认是session
设置系统变量
set [global | session] 系统变量名 = 值; set @@[global | session.]系统变量名 = 值;
注意:无论是全局设置还是会话设置,当mysql服务重启之后,之前配置都会失效。可以通过修改MySQL根目录下的
my.ini
配置文件达到永久修改的效果。(my.ini是MySQL数据库默认的系统级配置文件,默认是不存在的,需要新建,并参考一些资料进行配置。)
windows系统是my.ini、 linux系统是my.cnf, my.ini文件通常放在mysql安装的根目录下,如下图:
这个文件通常是不存在的,可以新建,新建后例如提供以下配置:
[mysqld] autocommit=0
这种配置就表示永久性关闭自动提交机制。(不建议这样做。)
用户自定义的变量。只在当前会话有效。所有的用户变量
'@'
开始。
给用户变量赋值
set @name = 'jackson';// 静态赋值 set @age := 30; set @gender := '男', @addr := '北京大兴区'; select @email := 'jackson@123.com'; select sal into @sal from emp where ename ='SMITH'; // 动态赋值
读取用户变量的值
select @name, @age, @gender, @addr, @email, @sal;
注意:mysql中变量不需要声明。直接赋值就行。如果没有声明变量,直接读取该变量,返回null
在存储过程中可以使用局部变量。使用
declare
声明。在begin和end之间有效。
变量的声明
declare 变量名 数据类型 [default ...];
变量的数据类型就是表字段的数据类型,例如:int、bigint、char、varchar、date、time、datetime等。
注意:declare通常出现在begin end之间的开始部分
。
变量的赋值
set 变量名 = 值; set 变量名 := 值; select 字段名 into 变量名 from 表名 ...;
案例:以下程序演示局部变量的声明、赋值、读取:
create PROCEDURE p2() begin /*声明变量*/ declare emp_count int default 0; /*声明变量*/ declare sal double(10,2) default 0.0; /*给变量赋值*/ select count(*) into emp_count from emp; /*给变量赋值*/ set sal := 5000.0; /*读取变量的值*/ select emp_count; /*读取变量的值*/ select sal; end;
call p2();
if 条件 then ...... elseif 条件 then ...... elseif 条件 then ...... else ...... end if;
案例:员工月薪sal,超过10000的属于“高收入”,6000到10000的属于“中收入”,少于6000的属于“低收入”。
create procedure p3( ) begin declare sal int default 5000; declare grade varchar(20); if sal > 10000 then set grade := '高收入'; elseif sal >= 6000 then set grade := '中收入'; else set grade := '低收入'; end if; select grade; end;
call p3();
存储过程的参数包括三种形式:
案例:员工月薪sal,超过10000的属于“高收入”,6000到10000的属于“中收入”,少于6000的属于“低收入”。
create procedure p4(in sal int, out grade varchar(20)) begin if sal > 10000 then set grade := '高收入'; elseif sal >= 6000 then set grade := '中收入'; else set grade := '低收入'; end if; end;
call p4(5000, @grade); select @grade;
案例:将传入的工资sal上调10%
create procedure p5(inout sal int) begin set sal := sal * 1.1; end;
set @sal := 10000; call p5(@sal); select @sal;
case 值 when 值1 then ...... when 值2 then ...... when 值3 then ...... else ...... end case;
case when 条件1 then ...... when 条件2 then ...... when 条件3 then ...... else ...... end case;
案例:根据不同月份,输出不同的季节。3 4 5月份春季。6 7 8月份夏季。9 10 11月份秋季。12 1 2 冬季。其他非法。
create procedure mypro(in month int, out result varchar(100)) begin case month when 3 then set result := '春季'; when 4 then set result := '春季'; when 5 then set result := '春季'; when 6 then set result := '夏季'; when 7 then set result := '夏季'; when 8 then set result := '夏季'; when 9 then set result := '秋季'; when 10 then set result := '秋季'; when 11 then set result := '秋季'; when 12 then set result := '冬季'; when 1 then set result := '冬季'; when 2 then set result := '冬季'; else set result := '非法月份'; end case; end;
create procedure mypro(in month int, out result varchar(100)) begin case when month = 3 or month = 4 or month = 5 then set result := '春季'; when month = 6 or month = 7 or month = 8 then set result := '夏季'; when month = 9 or month = 10 or month = 11 then set result := '秋季'; when month = 12 or month = 1 or month = 2 then set result := '冬季'; else set result := '非法月份'; end case; end;
call mypro(9, @season); select @season;
while 条件 do 循环体; end while;
案例:传入一个数字n,计算1~n中所有偶数的和。
create procedure mypro(in n int) begin declare sum int default 0; while n > 0 do if n % 2 = 0 then set sum := sum + n; end if; set n := n - 1; end while; select sum; end;
call mypro(10);
repeat 循环体; until 条件 end repeat;
注意:条件成立时结束循环
案例:传入一个数字n,计算1~n中所有偶数的和。
create procedure mypro(in n int, out sum int) begin set sum := 0; repeat if n % 2 = 0 then set sum := sum + n; end if; set n := n - 1; until n <= 0 end repeat; end;
call mypro(10, @sum); select @sum;
create procedure mypro() begin declare i int default 0; mylp:loop set i := i + 1; if i = 5 then leave mylp;-- 类似break作用 end if; select i; end loop; end;
create procedure mypro() begin declare i int default 0; mylp:loop set i := i + 1; if i = 5 then iterate mylp;-- 类似continue作用 end if; if i = 10 then leave mylp; end if; select i; end loop; end;
游标(cursor)可以理解为一个指向结果集中某条记录的指针,允许程序逐一访问结果集中的每条记录,并对其进行逐行操作和处理。
使用游标时,需要在存储过程或函数中定义一个游标变量,并通过 DECLARE
语句进行声明和初始化。然后,使用 OPEN
语句打开游标,使用 FETCH
语句逐行获取游标指向的记录,并进行处理。最后,使用 CLOSE
语句关闭游标,释放相关资源。游标可以大大地提高数据库查询的灵活性和效率。
声明游标的语法:
declare 游标名称 cursor for 查询语句;
打开游标的语法:
open 游标名称;
通过游标取数据的语法:
fetch 游标名称 into 变量[,变量,变量......]
关闭游标的语法:
close 游标名称;
案例:从dept表查询部门编号和部门名,创建一张新表dept2,将查询结果插入到新表中。
drop procedure if exists mypro; create procedure mypro() begin declare no int; declare name varchar(100); declare dept_cursor cursor for select deptno,dname from dept; drop table if exists dept2; create table dept2( no int primary key, name varchar(100) ); open dept_cursor; while true do fetch dept_cursor into no, name; insert into dept2(no,name) values(no,name); end while; close dept_cursor; end; call mypro();
执行结果:
出现了异常:异常信息中显示没有数据了。这是因为while true
循环导致的。
不过虽然出现了异常,但是表创建成功了,数据也插入成功了:
注意:声明局部变量和声明游标有顺序要求,局部变量的声明需要在游标声明之前完成。
DECLARE handler_name HANDLER FOR condition_value action_statement
给之前的游标添加异常处理机制:
drop procedure if exists mypro; create procedure mypro() begin declare no int; declare name varchar(100); declare dept_cursor cursor for select deptno,dname from dept; declare exit handler for not found close dept_cursor; drop table if exists dept2; create table dept2( no int primary key, name varchar(100) ); open dept_cursor; while true do fetch dept_cursor into no, name; insert into dept2(no,name) values(no,name); end while; close dept_cursor; end; call mypro();
存储函数:带
返回值
的存储过程
。参数只允许是in
(但不能写显示的写in)。没有out,也没有inout。
CREATE FUNCTION 存储函数名称(参数列表) RETURNS 数据类型 [特征] BEGIN --函数体 RETURN ...; END;
“特征”
的可取重要值如下:
案例:计算1~n的所有偶数之和
-- 删除函数 drop function if exists sum_fun; -- 创建函数 create function sum_fun(n int) returns int deterministic begin declare result int default 0; while n > 0 do if n % 2 = 0 then set result := result + n; end if; set n := n - 1; end while; return result; end; -- 调用函数 set @result = sum_fun(100); select @result;
MySQL 触发器是一种数据库
对象
,它是与表相关联的特殊程序。它可以在特定的数据操作(例如插入(INSERT)、更新(UPDATE)或删除(DELETE))触发时自动执行。MySQL 触发器使数据库开发人员能够在数据的不同状态之间维护一致性和完整性,并且可以为特定的数据库表自动执行操作。
触发器的作用主要有以下几个方面:
MySQL 触发器分为两种类型: BEFORE
和 AFTER
创建触发器的语法如下:
CREATE TRIGGER trigger_name BEFORE/AFTER INSERT/UPDATE/DELETE ON table_name FOR EACH ROW BEGIN -- 触发器执行的 SQL 语句 END;
其中:
需要注意的是,触发器是一种高级的数据库功能,只有在必要的情况下才应该使用,例如在需要实施强制性业务规则时。过多的触发器和复杂的触发器逻辑可能会影响查询性能和扩展性。
关于触发器的NEW和OLD关键字:
在 MySQL 触发器中,
NEW
和OLD
是两个特殊的关键字,用于引用在触发器中受到修改的行的新值
和旧值
。具体而言:
通俗的讲,NEW 是指触发器执行的操作所要插入或更新到当前行中的新数据;而 OLD 则是指当前行在触发器执行前原本的数据。
在MySQL 触发器中,NEW 和 OLD 使用方法是相似的。在触发器中,可以像引用表的其他列一样引用 NEW 和 OLD。例如,可以使用 OLD.column_name 从旧行中引用列值,也可以使用 NEW.column_name 从新行中引用列值。
示例:
假设有一个名为 my_table 的表,其中包含一个名为 quantity 的列。当在该表上执行 UPDATE 操作时,以下触发器会将旧值 OLD.quantity 累加到新值 NEW.quantity 中:
CREATE TRIGGER my_trigger BEFORE UPDATE ON my_table FOR EACH ROW BEGIN SET NEW.quantity = NEW.quantity + OLD.quantity; END;
在此触发器中,OLD.quantity 引用原始行的 quantity 值(旧值),而 NEW.quantity 引用更新行的 quantity 值(新值)。在触发器执行期间,数据行的 quantity 值将设置为旧值加上新值。
案例:当我们对dept表中的数据进行insert delete update的时候,请将这些操作记录到日志表当中,日志表如下:
drop table if exists oper_log; create table oper_log( id bigint primary key auto_increment, table_name varchar(100) not null comment '操作的哪张表', oper_type varchar(100) not null comment '操作类型包括insert delete update', oper_time datetime not null comment '操作时间', oper_id bigint not null comment '操作的那行记录的id', oper_desc text comment '操作描述' );
触发器1:向dept表中插入数据时,记录日志
create trigger dept_trigger_insert after insert on dept for each row begin insert into oper_log(id,table_name,oper_type,oper_time,oper_id,oper_desc) values (null,'dept','insert',now(),new.deptno,concat('插入数据:deptno=', new.deptno, ',dname=', new.dname,',loc=', new.loc)); end;
查看触发器:
show triggers;
删除触发器:
drop trigger if exists dept_trigger_insert;
向dept表中插入一条记录:
日志表中多了一条记录:
触发器2:修改dept表中数据时,记录日志
create trigger dept_trigger_update after update on dept for each row begin insert into oper_log(id,table_name,oper_type,oper_time,oper_id,oper_desc) values (null,'dept','update',now(),new.deptno,concat('更新前:deptno=', old.deptno, ',dname=', old.dname,',loc=', old.loc, ',更新后:deptno=', new.deptno, ',dname=', new.dname,',loc=', new.loc)); end;
更新一条记录:
update dept set loc = '北京' where deptno = 60;
日志表中多了一条记录:
注意:更新一条记录则对应一条日志。如果一次更新3条记录,那么日志表中插入3条记录。
触发器3:删除dept表中数据时,记录日志
create trigger dept_trigger_delete after delete on dept for each row begin insert into oper_log(id,table_name,oper_type,oper_time,oper_id,oper_desc) values (null,'dept','delete',now(),old.deptno,concat('删除了数据:deptno=', old.deptno, ',dname=', old.dname,',loc=', old.loc)); end;
删除一条记录:
delete from dept where deptno = 60;
日志表中多了一条记录: