1. 存储过程概念
能够将完成特定功能的SQL指令进行封装(SQL指令集),编译之后存储在数据库服务器上,并且为之取一个名字,客户端可以通过名字直接调用这个SQL指令集,获取执行结果。
2. 存储过程优缺点
2.1 优点
 (1)SQL指令无需经客户端编写通过网络传送,可以节省网络开销,同时避免使用SQL指令在网络传输过程中被恶意篡改从而保证安全性;
 (2) 存储过程是经过编译创建并保存在数据库中的,执行过程无需重复的进行编译操作,对SQL指令的执行过程进行了性能提升;
 (3)存储过程中多个指令之间可以存在逻辑关系,支持流程控制语句(分支、循环),能够实现较为复杂的业务。
 2.2 缺点
 (1)存储过程是根据不同的数据库进行编译、创建并存储在数据库中;但当需要切换到其他的数据库产品时,需要重新编写针对于新数据库的存储过程;
 (2)存储过程受限于数据库产品,如果需要高性能的优化则会成为一个问题;
 (3)在互联网项目中,如果需要数据库的高并发访问,使用存储过程会增加数据库的连接执行时间(因为将复杂业务交给了数据库进行处理)。
3. 创建存储过程
3.1 基本语法
CREATE PROCEDURE pro_name( [IN/OUT/INOUT param_name type] )
BEGIN
// ... 此处书写处理过程
END;
-- 创建存储过程
create procedure test1(IN a int, IN b int, OUT c int)
BEGIN
	SET c = a + b;
END;
-- 调用存储过程
set @total = 0;
call test1(2,4,@total);
-- 查询变量total值
select @total from dual;
3.2 存储过程中变量的使用
 存储过程中的变量分为2种:局部变量 和 用户变量。
(1)局部变量
 定义在存储过程中的变量,只能在存储过程的内部使用。
语法:DECLARE 变量名 变量类型 [default value];
-- 计算输入参数的平方和输入参数/2 之和
create procedure test2(IN a int, OUT sum int)
begin
	declare x int default 0;
	declare y int default 0;
	set x = a*a;
	set y = a/2;
	set sum = x + y;
end;
set @sum = 0; 
call test2(4, @sum);
select @sum from dual;
(2)用户变量
 相当于全局变量,定义的变量可以通过select @变量名 from dual;进行查询。
 用户变量会存储在mysql数据库的数据字典中(dual)。
 用户变量使用set关键字直接定义,格式 SET @变量名 = 值;
set @good = 111;
select @good from dual;
-- 创建存储过程
create procedure test1(IN a int, IN b int, OUT c int)
BEGIN
	SET c = a + b;
END;
-- 调用存储过程
set @total = 0;
call test1(2,4,@total);
-- 查询变量total值
select @total from dual;
(3)为变量赋值
 无论是局部变量还是用户变量,都是适应set关键字进行赋值。
(4)使用select...into...给变量赋值
 将查询结果赋值给变量。

// 将查询的结果赋值给变量
// set res = select count(num) from student;  
// 以上写法错误; 需通过select ... into... 语法来完成; 
create procedure get_stu_count(OUT res int)
begin
	select count(num) INTO res from student;  
end;
set @count = 0;
call get_stu_count(@count);
select @count from dual;
3.3 存储过程中的参数
MySQL存储过程中一共有三种参数:IN、OUT、INOUT。
/* person表 */
/*
 Navicat Premium Data Transfer
 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 80016
 Source Host           : localhost:3306
 Source Schema         : tempdb
 Target Server Type    : MySQL
 Target Server Version : 80016
 File Encoding         : 65001
 Date: 21/05/2023 20:03:29
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for person
-- ----------------------------
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person`  (
  `num` int(11) NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
(1)IN 参数
 输入参数,在调用存储过程中传递数据给存储过程的参数,在调用过程中传递时必须为 具有实际值的变量 或 字面值。
-- 创建存储过程, 插入记录
create procedure add_person(IN stu_num int, IN stu_name varchar(10), IN stu_sex varchar(10))
begin
	INSERT INTO person(num, name, sex) VALUES(stu_num, stu_name, stu_sex); 
end;
call add_person(101, 'zhang', '男'); 

(2)OUT参数
 输出参数,将存储过程中产生的数据返回给过程调用者,类似于java/c/c++中的函数的返回值,不同的是一个存储过程可以有多个输出参数。
-- 创建存储过程, 根据num查询学生姓名
create procedure get_person_by_num(IN stu_num int, OUT stu_name varchar(10))
begin
	SELECT name INTO stu_name FROM student WHERE num = stu_num;
end;
set @name = "";
call get_person_by_num(101, @name);
select @name from dual;

(3)INOUT参数
表示此参数既可以作为输入参数来使用,也可以作为输出参数来使用。
 不推荐使用,阅读性较差。
-- 创建存储过程,根据名字查询性别
create procedure get_sex_by_name(INOUT temp varchar(10))
begin
	select sex INTO temp from person where name = temp;
end;
set @name = "张三";
call get_sex_by_name(@name);
select @name from person; 

4. 存储过程中的流程控制
在存储过程中是 支持流程控制语句的, 用于实现逻辑的控制, 实现复杂的业务处理。
4.1 分支语句
(1)if…then…else…endif
if 条件 then
// SQL1
end if;
if 条件 then
// SQL1
else 
// SQL2
end if;
-- 创建存储过程
-- 如果参数为1, 插入一条: 否则插入2条
create procedure test1(IN a int)
begin
	if a=1 then
		insert into person(num, name) values(102, "李四");
	else 
		insert into person(num, name) values(105, "李四");
		insert into person(num, name) values(106, "王五");
	end if;
end;
call test1(1);
call test1(2);
(2)case语句
case 表达式
	when 值1 then 
	-- SQL1
	when 值2 then
	-- SQL2
	else 
	-- SQL3
end case;
-- 创建存储过程
create procedure test2(IN a int)
begin
	case a
		when 1 then
			insert into person(num, name) values(1022, "李四四");
		when 2 then 
			insert into person(num, name) values(1055, "毛五五");
			insert into person(num, name) values(1066, "望六六");
		else
			update person set num=1011111 where name = 'zhang';
	end case;
end;
call test2(1);
call test2(2);
call test2(3);
4.2 循环语句
(1)while语句
while 条件表达式 do
	-- SQL1
	-- SQL2
	-- ...
end while;
-- 创建存储过程, 利用while循环批量插入数据
create procedure test3(IN count int)
begin
	declare i int ;
	set i = 111; 
	while i<count do
		insert into person(num, name) values(i, '...');
		set i = i + 1;
	end while;
end;
call test3(115); 
(2)repeat语句
repeat
	-- SQL1
	-- SQL2
until 条件表达式
end repeat;
-- 创建存储过程, 利用repeat循环批量插入数据
create procedure test4(IN count int)
begin
	declare i int ;
	set i = 116; 
	repeat 
		insert into person(num, name) values(i, '...');
		set i =  i + 1;
	until i>count
	end repeat;
end;
call test4(120); 
(3)loop语句
// loop_label表示表注名;
// 通过leave关键字来退出loop循环;
loop_label: loop
	-- SQL1
	-- SQL2
	-- ...
	if 表达式 then
		leave loop_label;
	end if;
end loop;
-- 创建存储过程, 利用loop循环批量插入数据
create procedure test5(IN count int)
begin
	declare i int ;
	set i = 125; 
	MyLoop: loop 
		insert into person(num, name) values(i, '...');
		set i =  i + 1;
		if(i>count) then
			leave MyLoop;
		end if;
	end loop;
end;
call test5(130); 
5. 存储过程管理操作
存储过程是隶属于某个数据库的,只能在创建它的数据库中去操作它。
5.1 查询存储过程
-- 查询指定数据库中的所有存储过程
show procedure status where db='tempdb'; 
-- 查询存储过程的创建细节
show create procedure tempdb.test1;


5.2 修改存储过程
 指的是修改存储过程的特征/特性。
alter procedure 名字 特征1[特征2,特征3...]
存储过程的特征参数:
 (1)CONTAINS SQL 表示⼦程序包含 SQL 语句,但不包含读或写数据的语句
 (2)NO SQL 表示⼦程序中不包含 SQL 语句
 (3)READS SQL DATA 表示⼦程序中包含读数据的语句
 (4)MODIFIES SQL DATA 表示⼦程序中包含写数据的语句
 (5)SQL SECURITY { DEFINER |INVOKER } 指明谁有权限来执⾏
    DEFINER 表示只有定义者⾃⼰才能够执⾏
    INVOKER 表示调⽤者可以执⾏
 (6)COMMENT 'string' 表示注释信息
alter procedure test1 READS SQL DATA;
5.3 删除存储过程
drop procedure 名字;
-- 删除存储过程 test1
drop procedure test1;
6. 存储过程案例讲解
使用存储过程完成借书操作。
/*
 Navicat Premium Data Transfer
 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 80016
 Source Host           : localhost:3306
 Source Schema         : book_db
 Target Server Type    : MySQL
 Target Server Version : 80016
 File Encoding         : 65001
 Date: 21/05/2023 23:08:51
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for books
-- ----------------------------
DROP TABLE IF EXISTS `books`;
CREATE TABLE `books`  (
  `book_id` int(11) NOT NULL AUTO_INCREMENT,
  `book_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `book_author` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `book_price` decimal(10, 2) NOT NULL,
  `book_stock` int(11) NOT NULL,
  `book_desc` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`book_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of books
-- ----------------------------
INSERT INTO `books` VALUES (1, 'Java无难事', '孙鑫', 38.80, 12, '零基础讲解java');
INSERT INTO `books` VALUES (2, 'linux就该这么学', '鸟哥', 44.40, 9, '跟我从零开始学linux');
-- ----------------------------
-- Table structure for records
-- ----------------------------
DROP TABLE IF EXISTS `records`;
CREATE TABLE `records`  (
  `rid` int(11) NOT NULL AUTO_INCREMENT,
  `snum` char(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `bid` int(11) NOT NULL,
  `borrow_num` int(11) NOT NULL,
  `is_return` int(11) NOT NULL,
  `borrow_date` date NOT NULL,
  PRIMARY KEY (`rid`) USING BTREE,
  INDEX `FK_RECORDS_STUDENTS`(`snum`) USING BTREE,
  INDEX `FK_RECORDS_BOOKS`(`bid`) USING BTREE,
  CONSTRAINT `FK_RECORDS_BOOKS` FOREIGN KEY (`bid`) REFERENCES `books` (`book_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FK_RECORDS_STUDENTS` FOREIGN KEY (`snum`) REFERENCES `students` (`stu_num`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for students
-- ----------------------------
DROP TABLE IF EXISTS `students`;
CREATE TABLE `students`  (
  `stu_num` char(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `stu_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `stu_gender` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `stu_age` int(11) NOT NULL,
  PRIMARY KEY (`stu_num`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of students
-- ----------------------------
INSERT INTO `students` VALUES ('1001', '张三', '男', 20);
INSERT INTO `students` VALUES ('1002', '李四', '男', 20);
INSERT INTO `students` VALUES ('1003', '王五', '男', 20);
-- 实现借书业务:
-- 参数1: a 输入参数 学号
-- 参数2: b 输入参数 图书编号
-- 参数3: m 输入参数 借书的数量
-- 参数4: state 输出参数 借书的状态(1 借书成功,2 学号不存在,3 图书不存在,4 库存不足)
create procedure proc_borrow_book(IN a char(4), IN b int, IN m int, OUT state int)
begin
	 declare stu_count int default 0;
	 declare book_count int default 0;
	 declare stock int default 0;
 
	 -- 判断学号是否存在:根据参数 a 到学⽣信息表查询是否有stu_num=a的记录
	 select count(stu_num) INTO stu_count from students where stu_num=a;
	 if stu_count>0 then
		 -- 学号存在
		 -- 判断图书ID是否存在:根据参数b 查询图书记录总数
		 select count(book_id) INTO book_count from books where book_id=b;
		 if book_count >0 then
		 -- 图书存在
		 -- 判断图书库存是否充足?查询当前图书库存,然后和参数m进行比较
			 select book_stock INTO stock from books where book_id=b;
			 if stock >= m then
			 -- 执行借书
			 -- 操作1:在借书记录表中添加记录
			 insert into records(snum,bid,borrow_num,is_return,borrow_date) values(a,b,m,0,sysdate());
			 -- 操作2:修改图书库存
			 update books set book_stock=stock-m where book_id=b;
			 -- 借书成功
			 set state=1;
			 else
				 -- 库存不足
				 set state=4;
			 end if;
		 else
			 -- 图书不存在
			 set state = 3;
		 end if;
	 else
		 -- 不存在
		 set state = 2;
	 end if;
end;
-- 调用存储过程借书
set @state=0;
call proc_borrow_book('1001',1,2,@state);
select @state from dual;

7. 游标
游标可以⽤来依次取出查询结果集中的每⼀条数据——逐条读取查询结果集中的记录。
 就类似于c++、java中的迭代器。
7.1 使用步骤
 (1)声明游标
declare 游标名 cursor for select_statemnt;
(2)打开游标
open 游标名;
(3)使用游标
fetch 游标名 into var_name[,var_name] ... {参数名称};
(4)关闭游标
close 游标名;
-- 游标使用案例
create procedure use_cursor(OUT res varchar(100))
begin
	declare bname varchar(20);
	declare bauthor varchar(20);
	declare bprice decimal(10, 2);
	declare count int default 0;
	declare i int default 0;
	declare string varchar(100); 
	select count(1) INTO count from books;
	
-- 	声明游标
	declare MyCursor cursor for select book_name, book_author, book_price from books;
-- 	打开游标
	open MyCursor;
	while i < count do
-- 	使用游标, 提取游标当前指向的记录(提取后, 游标自动向下移)
		fetch MyCursor INTO bname, bauthor, bprice; 
		set i = i + 1;
-- 		set string = CONCAT_WS('#', bname, bauthor, bprice); 
		select CONCAT_WS('#', bname, bauthor, bprice) INTO string;
		set res = CONCAT_WS(',', res, string);
	end while;
-- 	关闭游标
	close MyCursor;	
	
end; 
set @temp="";
call use_cursor(@temp);
select @temp from dual;

8. 存储函数
8.1 基本语法
CREATE FUNCTION fun_name([arg1,arg2...])
RETURNS 类型
[characteristic...]
BEGIN
// ... 此处书写处理过程
END;
// RETURNS 表示函数返回数据的类型, 
// RETURNS 子句只能对FUNCTION做指定, 对于函数而言, 这是强制的;
// 它用来指定函数的返回类型, 而且函数体必须包含一个RETURN value语句;
// FUNCTION中参数总是默认为IN类型的, 不能是其他类型的(OUT/INOUT), 也不能显式的指定为IN类型的, 否则报错;
// 如何调用函数?
// 在MySQL中, 存储函数的使用方法与MySQL内部函数的使用方法是一致的.
-- 根据id查询对应的书名
drop function if exists test_fun1;
create function test_fun1(id int)
returns varchar(100)
READS SQL DATA
begin
	declare name varchar(20);
	select book_name INTO name from books where book_id=id;
	return name;
end;
select test_fun1(1); 
select test_fun1(2);
select test_fun1(22);



















