6.3 【案例讲解】存储过程
【题目 1】 创建无参存储过程proc1,查询所有学生的基本信息,显示字段:学号、姓名、性别、年龄、班级、籍贯。 -- 修改语句结束符
sql
DELIMITER //
-- 创建存储过程
CREATE PROCEDURE proc1()
BEGIN
-- 查询所有学生基础信息
SELECT
Sno AS 学号,
Sname AS 姓名,
Ssex AS 性别,
sage AS 年龄,
class AS 班级,
sbirthplace AS 籍贯
FROM student;
END //
-- 恢复默认结束符
DELIMITER ;-- 调用存储过程
sql
CALL proc1();-- 重写时先删除存储过程 -- DROP PROCEDURE IF EXISTS proc1; 【题目 2】 创建无参存储过程proc2,查询所有课程的完整信息,显示字段:课程号、课程名、授课教师姓名。
sql
DELIMITER //
CREATE PROCEDURE proc2()
BEGIN
-- 课程表左联教师表,兼容无授课教师的课程
SELECT
c.Cno AS 课程号,
c.Cname AS 课程名,
IFNULL(t.Tname,'无') AS 授课教师
FROM course c
LEFT JOIN teacher t ON c.Tno = t.Tno;
END //
DELIMITER ;-- 调用
sql
CALL proc2();【题目 3】 创建带输入参数的存储过程proc3,输入参数为学生学号in_sno,查询该学生的所有考试成绩,显示字段:课程名、考试分数。
sql
DELIMITER //
CREATE PROCEDURE proc3(IN in_sno VARCHAR(10))
BEGIN
-- 成绩表关联课程表,按学号筛选
SELECT
c.Cname AS 课程名,
sc.score AS 考试分数
FROM sc
LEFT JOIN course c ON sc.Cno = c.Cno
WHERE sc.Sno = in_sno;
END //
DELIMITER ;-- 调用示例:查询学号001学生的成绩
sql
CALL proc3('001');【题目 4】 创建带输入参数的存储过程proc4,输入参数为课程号in_cno,查询并显示该课程的考试平均分,结果保留 2 位小数。
sql
DELIMITER //
CREATE PROCEDURE proc4(IN in_cno VARCHAR(10))
BEGIN
-- 按课程号计算平均分
SELECT
c.Cname AS 课程名,
ROUND(AVG(sc.score),2) AS 课程平均分
FROM sc
LEFT JOIN course c ON sc.Cno = c.Cno
WHERE sc.Cno = in_cno
GROUP BY sc.Cno;
END //
DELIMITER ;-- 调用示例:查询01号课程的平均分
sql
CALL proc4('01');【题目 5】 创建带输入参数的存储过程proc5,输入参数为班级号in_class,查询该班级的所有学生名单,显示字段:学号、姓名、性别、年龄。
sql
DELIMITER //
CREATE PROCEDURE proc5(IN in_class CHAR(2))
BEGIN
-- 按班级号筛选学生
SELECT
Sno AS 学号,
Sname AS 姓名,
Ssex AS 性别,
sage AS 年龄
FROM student
WHERE class = in_class;
END //
DELIMITER ;-- 调用示例:查询03班的学生
sql
CALL proc5('03');【题目 6】 创建带输入和输出参数的存储过程proc6,输入参数为学生学号in_sno,输出参数为该学生的考试总科目数out_count。
sql
DELIMITER //
CREATE PROCEDURE proc6(
IN in_sno VARCHAR(10),
OUT out_count INT
)
BEGIN
-- 统计学生参考科目数,赋值给输出参数
SELECT COUNT(*) INTO out_count
FROM sc
WHERE Sno = in_sno;
END //
DELIMITER ;-- 调用示例:查询学号001的参考科目数
sql
CALL proc6('001', @count);-- 查看输出结果
sql
SELECT @count AS 总科目数;【题目 7】 创建带多个输入参数的存储过程proc7,输入参数为课程号in_cno、分数阈值in_score,查询该课程中分数大于等于阈值的学生信息,显示字段:学号、姓名、考试分数。
sql
DELIMITER //
CREATE PROCEDURE proc7(
IN in_cno VARCHAR(10),
IN in_score DECIMAL(18,1)
)
BEGIN
-- 多表联查,筛选达标学生
SELECT
s.Sno AS 学号,
s.Sname AS 姓名,
sc.score AS 考试分数
FROM sc
LEFT JOIN student s ON sc.Sno = s.Sno
WHERE sc.Cno = in_cno AND sc.score >= in_score;
END //
DELIMITER ;-- 调用示例:查询01号课程分数≥80分的学生
sql
CALL proc7('01', 80);【题目 8】 创建存储过程proc8,输入参数为学生学号in_sno、课程号in_cno,根据grades表的等级规则,判断并输出该学生该课程的成绩等级(优秀 / 良好 / 及格 / 不及格)。
sql
DELIMITER //
CREATE PROCEDURE proc8(
IN in_sno VARCHAR(10),
IN in_cno VARCHAR(10)
)
BEGIN
-- 定义变量存储成绩和等级
DECLARE stu_score DECIMAL(18,1);
DECLARE score_level VARCHAR(10); -- 查询学生对应课程的成绩
SELECT score INTO stu_score
FROM sc
WHERE Sno = in_sno AND Cno = in_cno;
-- 根据分数区间判断等级
IF stu_score >=90 AND stu_score <=100 THEN
SET score_level = '优秀';
ELSEIF stu_score >=75 AND stu_score <=89 THEN
SET score_level = '良好';
ELSEIF stu_score >=60 AND stu_score <=74 THEN
SET score_level = '及格';
ELSE
SET score_level = '不及格';
END IF;
-- 输出结果
SELECT
in_sno AS 学号,
in_cno AS 课程号,
stu_score AS 考试分数,
score_level AS 成绩等级;
END //
DELIMITER ;
-- 调用示例:查询001号学生02号课程的成绩等级
sql
CALL proc8('001','02');【题目 9】 创建存储过程proc9,输入参数为课程号in_cno,输出参数为该课程的最高分out_max、最低分out_min、平均分out_avg(平均分保留 2 位小数)。
sql
DELIMITER //
CREATE PROCEDURE proc9(
IN in_cno VARCHAR(10),
OUT out_max DECIMAL(18,1),
OUT out_min DECIMAL(18,1),
OUT out_avg DECIMAL(18,2)
)
BEGIN
-- 计算成绩统计指标,赋值给输出参数
SELECT
MAX(score),
MIN(score),
ROUND(AVG(score),2)
INTO out_max, out_min, out_avg
FROM sc
WHERE Cno = in_cno;
END //
DELIMITER ;-- 调用示例:查询01号课程的成绩统计
sql
CALL proc9('01', @max_score, @min_score, @avg_score);-- 查看结果
sql
SELECT
@max_score AS 最高分,
@min_score AS 最低分,
@avg_score AS 平均分;【题目 10】 创建存储过程proc10,输入参数为教师姓名in_tname,查询该教师所授课程的所有学生成绩明细,显示字段:教师姓名、课程名、学生学号、学生姓名、考试分数。
sql
DELIMITER //
CREATE PROCEDURE proc10(IN in_tname VARCHAR(10))
BEGIN
-- 四表联查:教师-课程-成绩-学生
SELECT
t.Tname AS 教师姓名,
c.Cname AS 课程名,
s.Sno AS 学生学号,
s.Sname AS 学生姓名,
sc.score AS 考试分数
FROM teacher t
LEFT JOIN course c ON t.Tno = c.Tno
LEFT JOIN sc ON c.Cno = sc.Cno
LEFT JOIN student s ON sc.Sno = s.Sno
WHERE t.Tname = in_tname;
END //
DELIMITER ;-- 调用示例:查询李老师的授课成绩明细
sql
CALL proc10('李老师');【题目 11】 创建存储过程proc11,输入参数为学生学号in_sno,判断该学生是否存在不及格科目,输出对应的提示信息(有不及格科目 / 无不及格科目 / 未查询到该学生成绩)。
sql
DELIMITER //
CREATE PROCEDURE proc11(IN in_sno VARCHAR(10))
BEGIN
-- 定义变量存储不及格科目数
DECLARE fail_count INT; -- 统计不及格科目数
SELECT COUNT(*) INTO fail_count
FROM sc
WHERE Sno = in_sno AND score < 60;
-- 多条件判断输出
IF NOT EXISTS (SELECT * FROM sc WHERE Sno = in_sno) THEN
SELECT '未查询到该学生成绩' AS 提示信息;
ELSEIF fail_count > 0 THEN
SELECT CONCAT('该学生有',fail_count,'门不及格科目') AS 提示信息;
ELSE
SELECT '该学生无不及格科目' AS 提示信息;
END IF;
END //
DELIMITER ;
-- 调用示例1:查询004号学生
sql
CALL 11('004');-- 调用示例2:查询001号学生
sql
CALL 11('001');【题目 12】 创建存储过程proc12,实现新增学生信息功能,输入参数为:学号、姓名、性别、出生日期、年龄、籍贯、班级,完成学生表的插入操作,执行后输出插入成功的提示。
sql
DELIMITER //
CREATE PROCEDURE proc12(
IN in_sno VARCHAR(10),
IN in_sname VARCHAR(10),
IN in_ssex VARCHAR(10),
IN in_sbirth DATE,
IN in_sage TINYINT,
IN in_sbirthplace VARCHAR(20),
IN in_class CHAR(2)
)
BEGIN
-- 插入学生数据
INSERT INTO student(Sno,Sname,Ssex,Sbirth,sage,sbirthplace,class)
VALUES (in_sno,in_sname,in_ssex,in_sbirth,in_sage,in_sbirthplace,in_class); -- 输出执行结果
SELECT '学生信息插入成功' AS 执行结果;
END //
DELIMITER ;
-- 调用示例:新增学号021的学生
sql
CALL proc12('021','张三','男','2008-05-01',16,'深圳','02');-- 验证插入结果
sql
SELECT * FROM student WHERE Sno = '021';【题目 13】 创建存储过程proc13,输入参数为班级号in_class,输出参数为该班级总人数out_total、男生人数out_male、女生人数out_female。
sql
DELIMITER //
CREATE PROCEDURE proc13(
IN in_class CHAR(2),
OUT out_total INT,
OUT out_male INT,
OUT out_female INT
)
BEGIN
-- 统计总人数
SELECT COUNT(*) INTO out_total
FROM student
WHERE class = in_class; -- 统计男生人数
SELECT COUNT(*) INTO out_male
FROM student
WHERE class = in_class AND Ssex = '男';
-- 统计女生人数
SELECT COUNT(*) INTO out_female
FROM student
WHERE class = in_class AND Ssex = '女';
END //
DELIMITER ;
-- 调用示例:统计03班的性别分布
sql
CALL proc13('03', @total, @male, @female);-- 查看结果
sql
SELECT
@total AS 班级总人数,
@male AS 男生人数,
@female AS 女生人数;【题目 14】 创建带循环结构的存储过程proc14,输入参数为正整数in_n,输出参数为 1 到该整数的累加和out_sum,使用 WHILE 循环实现。
sql
DELIMITER //
CREATE PROCEDURE proc14(
IN in_n INT,
OUT out_sum INT
)
BEGIN
-- 定义循环变量和累加变量
DECLARE i INT DEFAULT 1;
DECLARE sum_total INT DEFAULT 0; -- WHILE循环实现累加
WHILE i <= in_n DO
SET sum_total = sum_total + i;
SET i = i + 1;
END WHILE;
-- 赋值给输出参数
SET out_sum = sum_total;
END //
DELIMITER ;
-- 调用示例:计算1到100的累加和
sql
CALL proc14(100, @sum_result);-- 查看结果
sql
SELECT @sum_result AS 累加和结果;【题目 15】 创建存储过程proc15,实现成绩批量更新:输入参数为课程号in_cno、加分值in_add_score,给该课程所有学生的成绩加上指定分数,要求加分后最高分不超过 100 分,执行后输出更新的行数。
sql
DELIMITER //
CREATE PROCEDURE proc15(
IN in_cno VARCHAR(10),
IN in_add_score INT
)
BEGIN
-- 批量更新成绩,LEAST函数确保不超过100分
UPDATE sc
SET score = LEAST(score + in_add_score, 100)
WHERE Cno = in_cno; -- 输出更新的行数
SELECT ROW_COUNT() AS 成功更新的记录行数;
END //
DELIMITER ;
-- 调用示例:给01号课程所有学生加5分
sql
CALL proc15('01',5);-- 验证结果
sql
SELECT * FROM sc WHERE Cno = '01';【题目 16】 创建带游标的存储过程proc16,遍历所有有成绩的学生,统计每个学生的总成绩、平均成绩,将结果插入到student_score_stat统计表中。 -- 第一步:创建学生成绩统计表
sql
DROP TABLE IF EXISTS student_score_stat;
CREATE TABLE student_score_stat(
Sno VARCHAR(10) NOT NULL COMMENT '学号',
Sname VARCHAR(10) COMMENT '姓名',
total_score DECIMAL(18,1) COMMENT '总成绩',
avg_score DECIMAL(18,2) COMMENT '平均成绩',
PRIMARY KEY (Sno)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 第二步:创建带游标的存储过程
sql
DELIMITER //
CREATE PROCEDURE proc16()
BEGIN
-- 定义变量存储游标数据
DECLARE stu_sno VARCHAR(10);
DECLARE stu_sname VARCHAR(10);
DECLARE stu_total DECIMAL(18,1);
DECLARE stu_avg DECIMAL(18,2);
-- 定义游标结束标志
DECLARE done INT DEFAULT 0; -- 定义游标:查询学生成绩统计数据
DECLARE score_cursor CURSOR FOR
SELECT
s.Sno,
s.Sname,
SUM(sc.score),
ROUND(AVG(sc.score),2)
FROM student s
INNER JOIN sc ON s.Sno = sc.Sno
GROUP BY s.Sno;
-- 定义游标结束处理程序
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- 清空原有数据
TRUNCATE TABLE student_score_stat;
-- 打开游标
OPEN score_cursor;
-- 循环读取游标数据
read_loop:LOOP
FETCH score_cursor INTO stu_sno, stu_sname, stu_total, stu_avg;
-- 结束循环判断
IF done = 1 THEN
LEAVE read_loop;
END IF;
-- 插入统计数据
INSERT INTO student_score_stat(Sno,Sname,total_score,avg_score)
VALUES (stu_sno,stu_sname,stu_total,stu_avg);
END LOOP read_loop;
-- 关闭游标
CLOSE score_cursor;
-- 输出结果
SELECT '学生成绩统计完成' AS 执行结果;
END //
DELIMITER ;
-- 调用存储过程
sql
CALL proc16();-- 查看统计结果
sql
SELECT * FROM student_score_stat;【题目 17】 创建带事务和异常处理的存储过程proc17,实现学生转班功能:输入学生学号in_sno、新班级号in_new_class,更新学生班级,同时向operate_log表记录操作日志;执行出错则事务回滚,输出错误提示。 -- 第一步:创建操作日志表
sql
DROP TABLE IF EXISTS operate_log;
CREATE TABLE operate_log(
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID',
operate_type VARCHAR(20) COMMENT '操作类型',
operate_content VARCHAR(200) COMMENT '操作内容',
operate_time DATETIME DEFAULT NOW() COMMENT '操作时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 第二步:创建带事务和异常处理的存储过程
sql
DELIMITER //
CREATE PROCEDURE proc17(
IN in_sno VARCHAR(10),
IN in_new_class CHAR(2)
)
BEGIN
-- 定义变量存储学生原班级和姓名
DECLARE old_class CHAR(2);
DECLARE stu_name VARCHAR(10);
-- 定义异常处理:出错回滚并提示
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT '操作执行失败,已回滚' AS 执行结果;
END; -- 查询学生原信息
SELECT class, Sname INTO old_class, stu_name
FROM student
WHERE Sno = in_sno;
-- 开启事务
START TRANSACTION;
-- 更新学生班级
UPDATE student
SET class = in_new_class
WHERE Sno = in_sno;
-- 插入操作日志
INSERT INTO operate_log(operate_type,operate_content)
VALUES ('学生转班',CONCAT('学生',stu_name,'(学号',in_sno,')从',old_class,'班转入',in_new_class,'班'));
-- 提交事务
COMMIT;
-- 输出成功提示
SELECT '学生转班操作成功' AS 执行结果;
END //
DELIMITER ;
-- 调用示例:将001号学生从01班转入02班
sql
CALL proc17('001','02');-- 验证学生班级更新
sql
SELECT * FROM student WHERE Sno = '001';-- 查看操作日志
sql
SELECT * FROM operate_log;【题目 18】 创建存储过程proc18,输入参数为分数下限in_low、分数上限in_high,实现两个功能:1. 查询该分数区间内的所有学生成绩明细;2. 统计该区间内的成绩记录总条数。
sql
DELIMITER //
CREATE PROCEDURE proc_get_score_range(
IN in_low DECIMAL(18,1),
IN in_high DECIMAL(18,1)
)
BEGIN
-- 1. 查询成绩明细
SELECT
s.Sno AS 学号,
s.Sname AS 姓名,
c.Cname AS 课程名,
sc.score AS 考试分数
FROM sc
LEFT JOIN student s ON sc.Sno = s.Sno
LEFT JOIN course c ON sc.Cno = c.Cno
WHERE sc.score BETWEEN in_low AND in_high; -- 2. 统计记录总数
SELECT
CONCAT(in_low,'-',in_high,'分区间') AS 分数区间,
COUNT(*) AS 成绩记录总条数
FROM sc
WHERE score BETWEEN in_low AND in_high;
END //
DELIMITER ;
-- 调用示例:查询60-80分区间的成绩
sql
CALL proc18(60,80);【题目 19】 创建存储过程proc19,使用 CASE 多分支语句,实现部门表dept的增删改通用操作:输入操作类型in_operate_type(1 = 新增、2 = 修改、3 = 删除)、部门 IDin_dept_id、部门名称in_dept_name,执行对应逻辑并输出提示。
sql
DELIMITER //
CREATE PROCEDURE proc_dept_operate(
IN in_operate_type TINYINT,
IN in_dept_id INT,
IN in_dept_name VARCHAR(50)
)
BEGIN
-- 多分支判断操作类型
CASE in_operate_type
WHEN 1 THEN
-- 新增部门
INSERT INTO dept(id,name) VALUES (in_dept_id,in_dept_name);
SELECT '部门新增成功' AS 执行结果;
WHEN 2 THEN
-- 修改部门
UPDATE dept SET name = in_dept_name WHERE id = in_dept_id;
SELECT '部门修改成功' AS 执行结果;
WHEN 3 THEN
-- 删除部门
DELETE FROM dept WHERE id = in_dept_id;
SELECT '部门删除成功' AS 执行结果;
ELSE
-- 非法操作类型
SELECT '操作类型错误,仅支持1=新增、2=修改、3=删除' AS 错误提示;
END CASE;
END //
DELIMITER ;-- 调用示例1:新增部门,ID=7,名称=后勤部
sql
CALL proc19(1,7,'后勤部');-- 调用示例2:修改ID=7的部门名称为行政后勤部
sql
CALL proc19(2,7,'行政后勤部');-- 调用示例3:删除ID=7的部门
sql
CALL proc_dept_operate(3,7,'');【题目 20】综合实训题 创建存储过程proc20,输入参数为入职年份in_year、入职月份in_month,统计该年月入职的教师的授课情况,显示字段:教师姓名、入职时间、所授课程名、选课学生人数、课程平均分;无授课的教师也需显示,无数据字段显示为 “无”。
sql
DELIMITER //
CREATE PROCEDURE proc20(
IN in_year INT,
IN in_month INT
)
BEGIN
SELECT
t.Tname AS 教师姓名,
t.entrydate AS 入职时间,
IFNULL(c.Cname,'无') AS 所授课程名,
IFNULL(COUNT(sc.Sno),'无') AS 选课学生人数,
IFNULL(ROUND(AVG(sc.score),2),'无') AS 课程平均分
FROM teacher t
LEFT JOIN course c ON t.Tno = c.Tno
LEFT JOIN sc ON c.Cno = sc.Cno
WHERE YEAR(t.entrydate) = in_year AND MONTH(t.entrydate) = in_month
GROUP BY t.Tno, c.Cno;
END //
DELIMITER ;-- 调用示例:查询2023年7月入职的教师授课情况
sql
CALL proc20(2023,7);