游标练习题

第 7 章 · 存储过程与函数 · 共 10 题 · 游标遍历 / 嵌套游标 / 可更新游标

🛠 练习前准备:导入教学实例数据库

本练习使用「教学实例」数据库中的 student、course、sc、grades 表。请先导入建库脚本:课程资料 → 教学实例库(复制脚本执行,或下载 教学实例库.sql 后导入)。

1. 编写存储过程proc_course_list,使用游标遍历course表,逐行输出所有课程的课程号和课程名称。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_course_list;
DELIMITER //
CREATE PROCEDURE proc_course_list()
BEGIN
    -- 声明变量
    DECLARE v_cno VARCHAR(10);
    DECLARE v_cname VARCHAR(10);
    DECLARE done INT DEFAULT 0;
    
    -- 声明游标
    DECLARE cur_course CURSOR FOR 
        SELECT Cno, Cname FROM course;
    
    -- 声明结束处理程序
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    -- 打开游标
    OPEN cur_course;
    
    -- 循环读取
    course_loop: LOOP
        FETCH cur_course INTO v_cno, v_cname;
        IF done = 1 THEN
            LEAVE course_loop;
        END IF;
        
        -- 输出结果
        SELECT CONCAT('课程号:', v_cno, ',课程名:', v_cname) AS 课程信息;
    END LOOP course_loop;
    
    -- 关闭游标
    CLOSE cur_course;
END //
DELIMITER ;

-- 调用测试
CALL proc_course_list();

2. 编写存储过程proc_student_count_sex,使用游标遍历student表,统计并输出男生和女生的总人数。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_student_count_sex;
DELIMITER //
CREATE PROCEDURE proc_student_count_sex()
BEGIN
    DECLARE v_sex VARCHAR(10);
    DECLARE done INT DEFAULT 0;
    DECLARE male_count INT DEFAULT 0;
    DECLARE female_count INT DEFAULT 0;
    
    DECLARE cur_sex CURSOR FOR SELECT Ssex FROM student;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN cur_sex;
    sex_loop: LOOP
        FETCH cur_sex INTO v_sex;
        IF done = 1 THEN
            LEAVE sex_loop;
        END IF;
        
        IF v_sex = '男' THEN
            SET male_count = male_count + 1;
        ELSE
            SET female_count = female_count + 1;
        END IF;
    END LOOP sex_loop;
    CLOSE cur_sex;
    
    SELECT male_count AS 男生人数, female_count AS 女生人数;
END //
DELIMITER ;

3. 编写存储过程proc_grade_count,使用游标遍历sc表,统计并输出不及格、及格、良好、优秀四个等级的成绩数量(参考grades表的分数范围)。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_grade_count;
DELIMITER //
CREATE PROCEDURE proc_grade_count()
BEGIN
    DECLARE v_score DECIMAL(18,1);
    DECLARE done INT DEFAULT 0;
    -- 初始化四个等级的计数器
    DECLARE excellent INT DEFAULT 0;  -- 优秀(90-100)
    DECLARE good INT DEFAULT 0;       -- 良好(75-89)
    DECLARE pass INT DEFAULT 0;       -- 及格(60-74)
    DECLARE fail INT DEFAULT 0;       -- 不及格(0-59)
    
    DECLARE cur_score CURSOR FOR SELECT score FROM sc;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN cur_score;
    score_loop: LOOP
        FETCH cur_score INTO v_score;
        IF done = 1 THEN
            LEAVE score_loop;
        END IF;
        
        -- 根据分数判断等级
        IF v_score >= 90 THEN
            SET excellent = excellent + 1;
        ELSEIF v_score >= 75 THEN
            SET good = good + 1;
        ELSEIF v_score >= 60 THEN
            SET pass = pass + 1;
        ELSE
            SET fail = fail + 1;
        END IF;
    END LOOP score_loop;
    CLOSE cur_score;
    
    -- 输出统计结果
    SELECT 
        fail AS 不及格人数,
        pass AS 及格人数,
        good AS 良好人数,
        excellent AS 优秀人数;
END //
DELIMITER ;

-- 调用测试
CALL proc_grade_count();

4. 编写带参数的存储过程proc_student_by_class,输入班级号(如 '01'),使用游标遍历该班级的所有学生,输出他们的姓名和出生日期。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_student_by_class;
DELIMITER //
CREATE PROCEDURE proc_student_by_class(IN p_class CHAR(2))
BEGIN
    DECLARE v_name VARCHAR(10);
    DECLARE v_birth DATE;
    DECLARE done INT DEFAULT 0;
    
    -- 带参数的游标:根据输入的班级号筛选学生
    DECLARE cur_student CURSOR FOR 
        SELECT Sname, Sbirth FROM student WHERE class = p_class;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN cur_student;
    student_loop: LOOP
        FETCH cur_student INTO v_name, v_birth;
        IF done = 1 THEN
            LEAVE student_loop;
        END IF;
        
        SELECT CONCAT('姓名:', v_name, ',出生日期:', v_birth) AS 学生信息;
    END LOOP student_loop;
    CLOSE cur_student;
END //
DELIMITER ;

-- 调用测试:查询01班学生
CALL proc_student_by_class('01');

5. 编写存储过程proc_update_score,使用可更新游标遍历sc表,将所有数学课程(课程号 '02')的成绩加 3 分。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_update_score;
DELIMITER //
CREATE PROCEDURE proc_update_score()
BEGIN
    DECLARE v_sno VARCHAR(10);
    DECLARE v_cno VARCHAR(10);
    DECLARE v_score DECIMAL(18,1);
    DECLARE done INT DEFAULT 0;
    
    -- 声明可更新游标
    DECLARE cur_score CURSOR FOR 
        SELECT Sno, Cno, score FROM sc WHERE Cno = '02' FOR UPDATE;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN cur_score;
    score_loop: LOOP
        FETCH cur_score INTO v_sno, v_cno, v_score;
        IF done = 1 THEN
            LEAVE score_loop;
        END IF;
        
        -- 更新当前行数据
        UPDATE sc SET score = v_score + 3 
        WHERE CURRENT OF cur_score;
    END LOOP score_loop;
    CLOSE cur_score;
    
    SELECT '数学成绩已全部加3分' AS 操作结果;
END //
DELIMITER ;

6. 编写存储过程proc_delete_no_score,使用游标遍历course表,删除没有任何学生选修的课程(即sc表中没有对应记录的课程)。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_delete_no_score;
DELIMITER //
CREATE PROCEDURE proc_delete_no_score()
BEGIN
    DECLARE v_cno VARCHAR(10);
    DECLARE done INT DEFAULT 0;
    DECLARE v_count INT;
    
    -- 声明可更新游标
    DECLARE cur_course CURSOR FOR 
        SELECT Cno FROM course FOR UPDATE;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN cur_course;
    course_loop: LOOP
        FETCH cur_course INTO v_cno;
        IF done = 1 THEN
            LEAVE course_loop;
        END IF;
        
        -- 查询该课程是否有学生选修
        SELECT COUNT(*) INTO v_count FROM sc WHERE Cno = v_cno;
        
        -- 如果没有选修记录,删除当前行
        IF v_count = 0 THEN
            DELETE FROM course WHERE CURRENT OF cur_course;
        END IF;
    END LOOP course_loop;
    CLOSE cur_course;
    
    SELECT '已删除所有无人选修的课程' AS 操作结果;
END //
DELIMITER ;

-- 调用测试(会删除课程号04的计算机课程)
CALL proc_delete_no_score();

7. 编写存储过程proc_student_total_score,使用游标遍历所有学生,计算每个学生的总分,并将结果输出(学生姓名 + 总分)。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_student_total_score;
DELIMITER //
CREATE PROCEDURE proc_student_total_score()
BEGIN
    DECLARE v_sno VARCHAR(10);
    DECLARE v_name VARCHAR(10);
    DECLARE v_total DECIMAL(18,1);
    DECLARE done INT DEFAULT 0;
    
    -- 游标查询所有学生
    DECLARE cur_student CURSOR FOR 
        SELECT Sno, Sname FROM student;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN cur_student;
    student_loop: LOOP
        FETCH cur_student INTO v_sno, v_name;
        IF done = 1 THEN
            LEAVE student_loop;
        END IF;
        
        -- 计算该学生的总分
        SELECT IFNULL(SUM(score), 0) INTO v_total 
        FROM sc WHERE Sno = v_sno;
        
        SELECT CONCAT('姓名:', v_name, ',总分:', v_total) AS 学生总分;
    END LOOP student_loop;
    CLOSE cur_student;
END //
DELIMITER ;

-- 调用测试
CALL proc_student_total_score();

8. 编写存储过程proc_class_statistics,使用嵌套游标: 外层游标遍历所有班级 内层游标遍历每个班级的学生 统计并输出每个班级的总人数、男生人数、女生人数和平均年龄

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_class_statistics;
DELIMITER //
CREATE PROCEDURE proc_class_statistics()
BEGIN
    -- 外层游标变量:班级
    DECLARE v_class CHAR(2);
    DECLARE class_done INT DEFAULT 0;
    
    -- 内层游标变量:学生
    DECLARE v_sex VARCHAR(10);
    DECLARE v_age TINYINT UNSIGNED;
    DECLARE student_done INT DEFAULT 0;
    
    -- 统计变量
    DECLARE total_count INT;
    DECLARE male_count INT;
    DECLARE female_count INT;
    DECLARE total_age INT;
    DECLARE avg_age DECIMAL(5,2);
    
    -- 外层游标:遍历所有班级
    DECLARE cur_class CURSOR FOR 
        SELECT DISTINCT class FROM student WHERE class IS NOT NULL;
    
    -- 外层结束处理程序
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET class_done = 1;
    
    OPEN cur_class;
    class_loop: LOOP
        FETCH cur_class INTO v_class;
        IF class_done = 1 THEN
            LEAVE class_loop;
        END IF;
        
        -- 重置统计变量
        SET total_count = 0;
        SET male_count = 0;
        SET female_count = 0;
        SET total_age = 0;
        
        -- 内层游标:遍历当前班级的所有学生
        BEGIN
            DECLARE cur_student CURSOR FOR 
                SELECT Ssex, sage FROM student WHERE class = v_class;
            
            -- 内层结束处理程序(注意作用域)
            DECLARE CONTINUE HANDLER FOR NOT FOUND SET student_done = 1;
            
            OPEN cur_student;
            student_loop: LOOP
                FETCH cur_student INTO v_sex, v_age;
                IF student_done = 1 THEN
                    LEAVE student_loop;
                END IF;
                
                -- 统计数据
                SET total_count = total_count + 1;
                SET total_age = total_age + v_age;
                IF v_sex = '男' THEN
                    SET male_count = male_count + 1;
                ELSE
                    SET female_count = female_count + 1;
                END IF;
            END LOOP student_loop;
            CLOSE cur_student;
            
            -- 重置内层结束标志
            SET student_done = 0;
        END;
        
        -- 计算平均年龄
        SET avg_age = total_age / total_count;
        
        -- 输出当前班级统计结果
        SELECT 
            v_class AS 班级号,
            total_count AS 总人数,
            male_count AS 男生人数,
            female_count AS 女生人数,
            avg_age AS 平均年龄;
    END LOOP class_loop;
    CLOSE cur_class;
END //
DELIMITER ;

-- 调用测试
CALL proc_class_statistics();

9. 编写存储过程proc_course_grade_statistics,使用游标遍历每门课程,统计该课程各个成绩等级(优秀 / 良好 / 及格 / 不及格)的人数,并输出结果。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_course_grade_statistics;
DELIMITER //
CREATE PROCEDURE proc_course_grade_statistics()
BEGIN
    DECLARE v_cno VARCHAR(10);
    DECLARE v_cname VARCHAR(10);
    DECLARE course_done INT DEFAULT 0;
    
    DECLARE v_score DECIMAL(18,1);
    DECLARE score_done INT DEFAULT 0;
    
    DECLARE excellent INT;
    DECLARE good INT;
    DECLARE pass INT;
    DECLARE fail INT;
    
    -- 外层游标:遍历所有课程
    DECLARE cur_course CURSOR FOR 
        SELECT Cno, Cname FROM course;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET course_done = 1;
    
    OPEN cur_course;
    course_loop: LOOP
        FETCH cur_course INTO v_cno, v_cname;
        IF course_done = 1 THEN
            LEAVE course_loop;
        END IF;
        
        -- 重置计数器
        SET excellent = 0;
        SET good = 0;
        SET pass = 0;
        SET fail = 0;
        
        -- 内层游标:遍历当前课程的所有成绩
        BEGIN
            DECLARE cur_score CURSOR FOR 
                SELECT score FROM sc WHERE Cno = v_cno;
            
            DECLARE CONTINUE HANDLER FOR NOT FOUND SET score_done = 1;
            
            OPEN cur_score;
            score_loop: LOOP
                FETCH cur_score INTO v_score;
                IF score_done = 1 THEN
                    LEAVE score_loop;
                END IF;
                
                IF v_score >= 90 THEN
                    SET excellent = excellent + 1;
                ELSEIF v_score >= 75 THEN
                    SET good = good + 1;
                ELSEIF v_score >= 60 THEN
                    SET pass = pass + 1;
                ELSE
                    SET fail = fail + 1;
                END IF;
            END LOOP score_loop;
            CLOSE cur_score;
            
            SET score_done = 0;
        END;
        
        -- 输出当前课程统计结果
        SELECT 
            v_cname AS 课程名称,
            fail AS 不及格人数,
            pass AS 及格人数,
            good AS 良好人数,
            excellent AS 优秀人数;
    END LOOP course_loop;
    CLOSE cur_course;
END //
DELIMITER ;

-- 调用测试
CALL proc_course_grade_statistics();

10. 编写存储过程proc_top_student,使用游标遍历所有学生,计算每个学生的平均分,找出平均分最高的学生并输出其姓名和平均分。

查看参考答案(可复制)
sql
DROP PROCEDURE IF EXISTS proc_top_student;
DELIMITER //
CREATE PROCEDURE proc_top_student()
BEGIN
    DECLARE v_sno VARCHAR(10);
    DECLARE v_name VARCHAR(10);
    DECLARE v_avg DECIMAL(5,2);
    DECLARE done INT DEFAULT 0;
    
    -- 记录最高分和对应学生
    DECLARE max_avg DECIMAL(5,2) DEFAULT 0;
    DECLARE top_name VARCHAR(10);
    
    DECLARE cur_student CURSOR FOR 
        SELECT Sno, Sname FROM student;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    
    OPEN cur_student;
    student_loop: LOOP
        FETCH cur_student INTO v_sno, v_name;
        IF done = 1 THEN
            LEAVE student_loop;
        END IF;
        
        -- 计算该学生的平均分
        SELECT IFNULL(AVG(score), 0) INTO v_avg 
        FROM sc WHERE Sno = v_sno;
        
        -- 更新最高分记录
        IF v_avg > max_avg THEN
            SET max_avg = v_avg;
            SET top_name = v_name;
        END IF;
    END LOOP student_loop;
    CLOSE cur_student;
    
    -- 输出结果
    SELECT 
        top_name AS 最高分学生姓名,
        max_avg AS 平均分;
END //
DELIMITER ;

-- 调用测试(结果应该是赵雷,平均分89.67)
CALL proc_top_student();