以往我们使用数据库都是基于SQL去完成数据的CRUD,但其实,SQL是可编程的,我们可以把他当做一门编程语言一样去使用它,在其中我们可以:声明变量,执行SQL,进行逻辑判断,循环等等操作。为了达成这个目标,我们需要学习 存储过程,函数等数据库对象的操作。
常见的数据库对象有:表,索引,视图,存储过程,函数,用户,触发器 等等。
本文基于 MySQL8 作为测试环境。
为了完成SQL的编程,我们需要先学习一些基础的语法:
MySQL中变量分为三种类型: 系统变量、用户变量、局部变量。
系统变量是MySQL数据库系统提供的,不是用户来定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)。
查看变量的语法是:
-- 查询所有系统变量
show global variables;
show global variables like 'xx'; -- 模糊匹配
-- 查看指定的系统变量
select @@[global | session].变量名;
例如:
select @@global.autocommit;
select @@session.autocommit;
用户定义变量是用户根据需要自己定义的变量,使用用户变量时不需要提前声明,在用的时候直接用 @变量
使用就可以,其作用域为当前会话,即在会话1中定义的变量在会话2中是不能使用的。
-- 声明并赋值
set @变量名 = 值; 或者 set @变量名 := 值;
-- 案例1:
set @number = 1; -- 为number变量设置值为1
select @number; -- 查询出number的值
-- 案例2:把查询出来的数据赋值给变量
select 字段名 into @变量名 from 表名 where 条件;
局部变量是用户根据需要自己定义的变量,仅在当前SQL代码局部有效(在后续存储过程中具体使用)。
MySQL 中可以使用 declare 关键字来定义局部变量,其基本语法如下:
declare 变量名 数据类型 default 默认值;
例如:declare age int(11) default 30;
为变量赋值:
-- 方式1: 使用 SET 关键字来为变量赋值
declare a int default 0;
set a = 10;
-- 方式2:使用 select ... into 语句为变量赋值
declare a int default 0;
select age into a from user where id = 1;
单分支:
if(条件) then
条件满足执行的语句
end if;
双分支:
if(条件) then
条件满足执行的语句
else
条件不满足执行的语句
end if;
多路分支:
if(条件1) then
满足条件1执行的语句;
elseif(条件2) then
满足条件2执行的语句;
else
否则执行的语句;
end if;
case 语句也是用来进行条件判断
case case_value
when 值1 then 当case_value和值1相等时执行的语句;
when 值2 then 当case_value和值2相等时执行的语句;
...
else 当case_value和任何值都不相等时执行的语句;
end case;
while 条件 do
满足条件执行的语句;
end while;
loop也是循环结构,只不过他没有循环条件。
[begin_label:]loop
循环执行的代码
end loop [end_label];
-- begin_label参数和 end_label参数分别表示循环开始和结束的标志,这两个标志必须相同,而且都可以省略。
-- 案例:循环对count+1(死循环)。
add_num:loop
set @count=@count+1;
end loop add_num;
-- 停止loop需要使用leave,该案例表示,当count为100时停止循环。
add_num:loop
set @count=@count+1;
if @count=100 then
leave add_num;
end loop add_num;
break
和 continue
repeat语句是有条件控制的循环语句,每次语句执行完毕后,会对条件表达式进行判断,如果表达式返回值为 TRUE,则循环结束,否则重复执行循环中的语句。
[begin_label:] repeat
statement_list
until search_condition
end repeat[end_label]
案例:循环执行 count 加 1 的操作,count 值为 100 时结束循环。
repeat
set @count=@count+1;
until @count=100
end repeat;
存储过程:是一组为了完成特定功能的SQL语句的集合,一般用于报表统计,数据迁移等等。
准备测试数据:
DROP TABLE IF EXISTS student;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(50) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`score` double(4,1) DEFAULT NULL COMMENT '分数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `student` VALUES (1, '张三', 18, 50.0);
INSERT INTO `student` VALUES (2, '李四', 20, 65.0);
INSERT INTO `student` VALUES (3, '王五', 20, 75.0);
INSERT INTO `student` VALUES (4, '赵六', 22, 85.0);
存储过程命名建议:p_存储过程名
create procedure 存储过程名(参数列表)
begin
封装的sql语句
end;
-- 存储过程名命名建议:p_存储过程名
注意:命令行下执行命令时,因为只要输了分号就表示语句结束了,所以,创建存储过程时需要执行命令:
delimiter $$
表示开启一个代码段,在没有输入 $$
时,表示整个代码段还未结束,就算输了分号也没事。
所以在命令行下应该这么写:
delimiter $$
create procedure 存储过程名(参数列表)
begin
封装的sql语句(可以多条)
end;
$$
案例: 创建一个简单的存储过程,封装一条sql,不含参数
delimiter $$
create procedure p1()
begin
select * from student;
end;
$$
-- 查询所有存储过程的信息
show procedure status;
-- 查询指定存储过程的信息
show procedure status like '存储过程名';
-- 查询存储过程的创建详情
show create procedure 存储过程名;
call 存储过程名(参数列表);
drop procedure [if exists] 存储过程名;
create procedure p2()
begin
declare my_name varchar(50) default '';
select name into my_name from student where id=2;
select my_name;
end;
其中语法说明:
1. 声明变量:declare 变量名 变量类型 default 默认值
2. into : 将值赋值给指定变量
案例1:IN 入参的使用
create procedure p3(IN my_id int)
begin
declare my_name varchar(50) default '';
select name into my_name from student where id=my_id;
select my_name;
end;
-- 语法说明:IN 是关键字,表示入参的意思,即my_id用于接收用户传入的参数
案例2:OUT 出参的使用
-- 创建存储过程
create procedure p4(IN my_id int,out my_name varchar(50))
begin
select name into my_name from student where id=my_id;
end;
-- 调用存储过程
call p4(2,@my_name);
-- 查询返回值
select @my_name;
案例3:INOUT 的使用,INOUT 修饰的变量既是入参,也是出参。
-- 创建存储过程。需求是:将传入的分数*0.5后返回
create procedure p5(INOUT score double)
begin
set score := score * 0.5;
end;
-- 声明变量
set @score = 60;
-- 调用存储过程
call p5(@score);
-- 查看返回值
select @score; -- 结果为:30
案例1:id为偶数查询对应的name值,id为奇数直接返回id值。
create procedure p6(IN my_id int)
begin
declare my_name varchar(50) default '';
if(my_id%2=0) then
select name into my_name from student where id = my_id;
select my_name;
else
select my_id;
end if;
end;
案例2:根据用户id查询其成绩等级
-- 创建
create procedure p7(IN my_id int,OUT level varchar(50))
begin
declare my_score int default 0;
select score into my_score from student where id = my_id;
if(my_score<60)
then set level = "不及格";
elseif(my_score<80)
then set level = "良好";
else
set level = "优秀";
end if;
end;
-- 调用
call p7(2,@level);
-- 获取等级
select @level;
mysql中有内置函数,比如: now()
,当然我们也可以自己封装!
注意:mysql8在创建函数前需要执行如下设置,否则要报错。
set global log_bin_trust_function_creators=true;
创建函数的语法:
create function 函数名(参数列表) returns 返回值类型
begin
执行语句;
return ...;
end;
注意:函数必须有返回值!
案例:封装一个根据id查询name的自定义函数:
create function getName(my_id int) returns varchar(50)
begin
declare my_name varchar(32) default '';
select name into my_name from student where id=my_id;
return my_name;
end;
select 函数名(实参);
drop function 函数名;
概念:根据对表的操作自动触发一些动作,触发条件可以是:insert,update,delete。
准备测试数据:
DROP TABLE IF EXISTS log;
CREATE TABLE `log` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`action` varchar(50) DEFAULT NULL COMMENT '动作(inset,update,delete)',
`sid` int(11) DEFAULT NULL COMMENT '学员id',
`time` datetime DEFAULT NULL COMMENT '时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
触发器命名建议:trg_触发器名
create trigger 触发器名 after 操作 on 表名
for each row
begin
触发后执行的语句;
end;
说明:
after
表示之后,也可以使用before 表示之前操作 on 表名
: 表示对哪个表做什么操作时触发,操作有:insert,update,deletefor each row
: 表示行级别案例:对student表插入数据之后,自动触发执行sql语句往log表中写入日志数据。
create trigger student_insert_trigger after insert on student
for each row
begin
insert into log(action,sid,time) values('insert',NEW.id,now());
end;
-- 参数解释:NEW中包含的是新插入的数据,OLD 中包含老数据
-- 这的NEW.id就表示获取刚插入的学员数据的id主键
案例:创建触发器,在student表执行删除时,将删除的数据的id记录到log表中。
create trigger student_delete_trigger after delete on student
for each row
begin
insert into log(action,sid,time) values('delete',OLD.id,now());
end;
show triggers;
drop trigger 触发器名;
在 MySQL 中,存储过程或函数中的查询有时会返回多条记录,而使用简单的 SELECT 语句,没有办法得到第一行、下一行,这时可以使用游标来逐条读取查询结果集中的记录,游标一般配合循环使用。
在MySQL中,游标只能用于存储过程和函数。
-- 创建游标
declare 游标名 cursor for 查询语句;
-- 打开游标
open 游标名;
-- 使用游标(读取结果集中的记录,并赋值给变量)
fetch 游标名 into 变量1,变量2,变量3...
-- 关闭游标
close 游标名;
定义一个函数,查询出学生表中的所有记录,并拼接为字符串返回。
create function selectAllStudent() returns text
begin
-- 定义变量用于存储学生表的数据
declare my_id int default 0;
declare my_name varchar(50) default "";
declare my_age int default 0;
declare my_score double default 0.0;
-- 用户存储全部数据
declare my_result text default "";
-- 用于存储一行数据
declare temp_str text default "";
declare i int default 0;
-- 声明变量用于存储student表总记录数
declare my_count int default 0;
-- 定义游标
declare my_cursor cursor for select id,name,age,score from student;
-- 查询总记录数
SELECT COUNT(1) INTO my_count FROM student;
-- 打开游标
open my_cursor;
-- 使用游标循环遍历结果集
set i = 0;
while i<my_count do
-- 获取一行数据并赋值给变量
fetch my_cursor into my_id,my_name,my_age,my_score;
-- 将每一条数据拼到temp_str中
select concat_ws(",",my_id,my_name,my_age,my_score) into temp_str;
-- 将数据统一累加到my_result
set my_result=concat_ws('|',my_result,temp_str);
-- i累加
set i = i+1;
end while;
-- 关闭游标
close my_cursor;
-- 返回结果
return my_result;
end;
执行该函数会将表中数据拼为一个串返回,表中数据如下:
+----+------+------+-------+
| id | name | age | score |
+----+------+------+-------+
| 1 | 张三 | 18 | 70.0 |
| 2 | 李四 | 20 | 80.0 |
| 3 | 王五 | 20 | 95.0 |
+----+------+------+-------+
执行结果:
|1,张三,18,70|2,李四,20,80|3,王五,20,95