第 7 章:视图与存储过程
在日常的数据库操作中,我们经常会遇到需要重复执行的复杂查询,或者需要封装一些业务逻辑的情况。MySQL 提供了视图和存储过程来解决这些问题。视图可以简化复杂查询,存储过程可以封装业务逻辑。
本章内容概要
- 视图的概念与作用
- 创建、修改、删除视图
- 视图的优缺点
- 什么是存储过程?
- 创建与调用存储过程
- 存储过程中的变量与参数
- 存储函数
- 触发器基础
- 实战练习:创建常用视图和存储过程
视图的概念与作用
视图(View)是一种虚拟表,它基于 SQL 语句的结果集。视图包含行和列,就像真实的表一样,但视图中的数据来自于一个或多个真实的表。
视图的优点
- 简化复杂查询 - 将复杂的 SQL 查询封装成简单的视图
- 数据安全 - 只暴露需要的列和行,隐藏敏感数据
- 逻辑独立性 - 当底层表结构发生变化时,只需修改视图定义
- 重用性 - 同一个视图可以被多个应用程序使用
视图的缺点
- 性能开销 - 查询视图时需要执行底层的 SQL 语句
- 更新限制 - 并非所有视图都可以更新
- 调试困难 - 视图的错误可能难以追踪
创建、修改、删除视图
创建视图
sql
USE student_management;
-- 创建学生基本信息视图
CREATE VIEW v_student_basic AS
SELECT
id,
student_number AS 学号,
name AS 姓名,
gender AS 性别,
major AS 专业,
class_name AS 班级
FROM students
WHERE is_active = TRUE;
-- 创建学生成绩统计视图
CREATE VIEW v_student_statistics AS
SELECT
s.id AS student_id,
s.student_number AS 学号,
s.name AS 姓名,
s.major AS 专业,
COUNT(g.id) AS 选课数,
ROUND(AVG(g.grade), 2) AS 平均分,
MAX(g.grade) AS 最高分,
MIN(g.grade) AS 最低分
FROM students s
LEFT JOIN grades g ON s.id = g.student_id
GROUP BY s.id, s.student_number, s.name, s.major;
-- 创建课程成绩统计视图
CREATE VIEW v_course_statistics AS
SELECT
c.id AS course_id,
c.course_code AS 课程代码,
c.course_name AS 课程名称,
c.credits AS 学分,
COUNT(g.id) AS 参考人数,
ROUND(AVG(g.grade), 2) AS 平均分,
MAX(g.grade) AS 最高分,
MIN(g.grade) AS 最低分
FROM courses c
LEFT JOIN grades g ON c.id = g.course_id
GROUP BY c.id, c.course_code, c.course_name, c.credits;使用视图
sql
-- 查询所有在读学生基本信息
SELECT * FROM v_student_basic;
-- 查询平均分在85分以上的学生
SELECT * FROM v_student_statistics
WHERE 平均分 >= 85
ORDER BY 平均分 DESC;
-- 查询平均分低于70分的课程
SELECT * FROM v_course_statistics
WHERE 平均分 < 70
ORDER BY 平均分 ASC;修改视图
sql
-- 修改视图定义
ALTER VIEW v_student_basic AS
SELECT
id,
student_number AS 学号,
name AS 姓名,
gender AS 性别,
major AS 专业,
class_name AS 班级,
enrollment_date AS 入学日期
FROM students
WHERE is_active = TRUE;删除视图
sql
-- 删除视图
DROP VIEW IF EXISTS v_student_basic;
-- 删除多个视图
DROP VIEW IF EXISTS v_student_basic, v_student_statistics;可更新视图
某些视图是可以更新的,但需要满足特定条件:
sql
-- 创建可更新视图
CREATE VIEW v_active_students AS
SELECT id, student_number, name, major
FROM students
WHERE is_active = TRUE;
-- 通过视图更新数据
UPDATE v_active_students
SET major = '计算机科学与技术'
WHERE student_number = '2023001';
-- 通过视图插入数据
INSERT INTO v_active_students (student_number, name, major)
VALUES ('2023008', '新学生', '软件工程');
-- 通过视图删除数据
DELETE FROM v_active_students
WHERE student_number = '2023008';什么是存储过程?
存储过程(Stored Procedure)是一组为了完成特定功能的 SQL 语句集合,它存储在数据库中,可以被应用程序调用执行。
存储过程的优点
- 提高性能 - 预编译,执行速度快
- 减少网络流量 - 只需传递存储过程名和参数
- 增强安全性 - 可以控制用户对数据的访问
- 代码重用 - 可以被多个应用程序调用
- 模块化 - 将复杂的业务逻辑封装起来
存储过程的缺点
- 调试困难 - 比普通 SQL 语句难调试
- 移植性差 - 不同数据库的存储过程语法不同
- 占用服务器资源 - 存储过程在数据库服务器上执行
创建与调用存储过程
创建简单的存储过程
sql
-- 创建一个简单的存储过程
DELIMITER //
CREATE PROCEDURE sp_get_all_students()
BEGIN
SELECT * FROM students;
END //
DELIMITER ;
-- 调用存储过程
CALL sp_get_all_students();带参数的存储过程
sql
-- 创建带输入参数的存储过程
DELIMITER //
CREATE PROCEDURE sp_get_students_by_major(IN major_name VARCHAR(50))
BEGIN
SELECT * FROM students WHERE major = major_name;
END //
DELIMITER ;
-- 调用带参数的存储过程
CALL sp_get_students_by_major('计算机科学与技术');带输出参数的存储过程
sql
-- 创建带输出参数的存储过程
DELIMITER //
CREATE PROCEDURE sp_get_student_count_by_major(
IN major_name VARCHAR(50),
OUT student_count INT
)
BEGIN
SELECT COUNT(*) INTO student_count
FROM students
WHERE major = major_name;
END //
DELIMITER ;
-- 调用带输出参数的存储过程
CALL sp_get_student_count_by_major('计算机科学与技术', @count);
SELECT @count AS 学生人数;带输入输出参数的存储过程
sql
-- 创建带输入输出参数的存储过程
DELIMITER //
CREATE PROCEDURE sp_update_student_major(
INOUT student_id INT,
IN new_major VARCHAR(50)
)
BEGIN
UPDATE students
SET major = new_major
WHERE id = student_id;
SELECT name INTO student_id
FROM students
WHERE id = student_id;
END //
DELIMITER ;
-- 调用带输入输出参数的存储过程
SET @student_id = 1;
CALL sp_update_student_major(@student_id, '软件工程');
SELECT @student_id AS 学生姓名;存储过程中的变量与控制结构
变量声明和使用
sql
-- 创建包含变量的存储过程
DELIMITER //
CREATE PROCEDURE sp_student_statistics()
BEGIN
DECLARE total_students INT DEFAULT 0;
DECLARE avg_grade DECIMAL(5,2) DEFAULT 0;
DECLARE max_grade DECIMAL(5,2) DEFAULT 0;
SELECT COUNT(*) INTO total_students FROM students;
SELECT AVG(grade) INTO avg_grade FROM grades;
SELECT MAX(grade) INTO max_grade FROM grades;
SELECT
total_students AS 总学生数,
avg_grade AS 平均成绩,
max_grade AS 最高成绩;
END //
DELIMITER ;
-- 调用存储过程
CALL sp_student_statistics();条件控制结构
sql
-- 创建包含条件判断的存储过程
DELIMITER //
CREATE PROCEDURE sp_get_grade_level(IN student_id INT)
BEGIN
DECLARE avg_grade DECIMAL(5,2) DEFAULT 0;
DECLARE grade_level VARCHAR(20) DEFAULT '';
SELECT AVG(grade) INTO avg_grade
FROM grades
WHERE grades.student_id = student_id;
IF avg_grade >= 90 THEN
SET grade_level = '优秀';
ELSEIF avg_grade >= 80 THEN
SET grade_level = '良好';
ELSEIF avg_grade >= 70 THEN
SET grade_level = '中等';
ELSEIF avg_grade >= 60 THEN
SET grade_level = '及格';
ELSE
SET grade_level = '不及格';
END IF;
SELECT grade_level AS 成绩等级;
END //
DELIMITER ;
-- 调用存储过程
CALL sp_get_grade_level(1);循环控制结构
sql
-- 创建包含循环的存储过程
DELIMITER //
CREATE PROCEDURE sp_insert_test_students()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 10 DO
INSERT INTO students (student_number, name, enrollment_date, major)
VALUES (
CONCAT('TEST', LPAD(i, 3, '0')),
CONCAT('测试学生', i),
CURDATE(),
'测试专业'
);
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
-- 调用存储过程
CALL sp_insert_test_students();存储函数
存储函数(Stored Function)与存储过程类似,但函数必须返回一个值。
创建存储函数
sql
-- 创建计算平均分的函数
DELIMITER //
CREATE FUNCTION fn_get_avg_grade(student_id INT)
RETURNS DECIMAL(5,2)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE avg_grade DECIMAL(5,2) DEFAULT 0;
SELECT AVG(grade) INTO avg_grade
FROM grades
WHERE grades.student_id = student_id;
RETURN IFNULL(avg_grade, 0);
END //
DELIMITER ;
-- 使用存储函数
SELECT
name AS 学生姓名,
fn_get_avg_grade(id) AS 平均分
FROM students
ORDER BY 平均分 DESC;创建复杂函数
sql
-- 创建获取学生等级的函数
DELIMITER //
CREATE FUNCTION fn_get_student_level(student_id INT)
RETURNS VARCHAR(20)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE avg_grade DECIMAL(5,2) DEFAULT 0;
SELECT AVG(grade) INTO avg_grade
FROM grades
WHERE grades.student_id = student_id;
IF avg_grade >= 90 THEN
RETURN '优秀';
ELSEIF avg_grade >= 80 THEN
RETURN '良好';
ELSEIF avg_grade >= 70 THEN
RETURN '中等';
ELSEIF avg_grade >= 60 THEN
RETURN '及格';
ELSE
RETURN '不及格';
END IF;
END //
DELIMITER ;
-- 使用复杂函数
SELECT
name AS 学生姓名,
fn_get_avg_grade(id) AS 平均分,
fn_get_student_level(id) AS 等级
FROM students
ORDER BY 平均分 DESC;触发器基础
触发器(Trigger)是一种特殊的存储程序,它在特定的事件(INSERT、UPDATE、DELETE)发生时自动执行。
创建触发器
sql
-- 创建插入学生时的触发器
DELIMITER //
CREATE TRIGGER tr_before_insert_student
BEFORE INSERT ON students
FOR EACH ROW
BEGIN
-- 确保学号不为空
IF NEW.student_number IS NULL OR NEW.student_number = '' THEN
SET NEW.student_number = CONCAT('AUTO', UNIX_TIMESTAMP());
END IF;
-- 设置默认入学日期
IF NEW.enrollment_date IS NULL THEN
SET NEW.enrollment_date = CURDATE();
END IF;
END //
DELIMITER ;
-- 创建更新成绩时的触发器
DELIMITER //
CREATE TRIGGER tr_after_update_grade
AFTER UPDATE ON grades
FOR EACH ROW
BEGIN
-- 记录成绩变更日志
INSERT INTO grade_logs (student_id, course_id, old_grade, new_grade, update_time)
VALUES (NEW.student_id, NEW.course_id, OLD.grade, NEW.grade, NOW());
END //
DELIMITER ;管理触发器
sql
-- 查看触发器
SHOW TRIGGERS;
-- 删除触发器
DROP TRIGGER IF EXISTS tr_before_insert_student;实战练习:创建常用视图和存储过程
让我们创建一些实用的视图和存储过程来管理学生信息。
1. 创建综合视图
sql
-- 创建完整的学生信息视图
CREATE VIEW v_student_full_info AS
SELECT
s.id AS 学生ID,
s.student_number AS 学号,
s.name AS 姓名,
s.gender AS 性别,
s.birth_date AS 出生日期,
s.phone AS 电话,
s.email AS 邮箱,
s.major AS 专业,
s.class_name AS 班级,
s.enrollment_date AS 入学日期,
COUNT(g.id) AS 选课数,
ROUND(AVG(g.grade), 2) AS 平均分,
MAX(g.grade) AS 最高分,
MIN(g.grade) AS 最低分
FROM students s
LEFT JOIN grades g ON s.id = g.student_id
GROUP BY
s.id, s.student_number, s.name, s.gender, s.birth_date,
s.phone, s.email, s.major, s.class_name, s.enrollment_date;
-- 使用视图查询
SELECT * FROM v_student_full_info
ORDER BY 平均分 DESC;2. 创建管理存储过程
sql
-- 创建添加学生成绩的存储过程
DELIMITER //
CREATE PROCEDURE sp_add_student_grade(
IN student_id INT,
IN course_id INT,
IN grade_value DECIMAL(5,2),
IN exam_date DATE,
IN semester VARCHAR(20)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
INSERT INTO grades (student_id, course_id, grade, exam_date, semester)
VALUES (student_id, course_id, grade_value, exam_date, semester);
COMMIT;
SELECT '成绩添加成功' AS 消息;
END //
DELIMITER ;
-- 调用存储过程
CALL sp_add_student_grade(1, 3, 87.5, '2023-12-25', '2023秋季');3. 创建统计存储过程
sql
-- 创建生成成绩报告的存储过程
DELIMITER //
CREATE PROCEDURE sp_generate_grade_report()
BEGIN
-- 创建临时表存储报告数据
CREATE TEMPORARY TABLE temp_report (
统计项目 VARCHAR(50),
统计值 VARCHAR(50)
);
-- 插入统计数据
INSERT INTO temp_report VALUES
('总学生数', (SELECT COUNT(*) FROM students)),
('总课程数', (SELECT COUNT(*) FROM courses)),
('总成绩记录数', (SELECT COUNT(*) FROM grades)),
('平均成绩', (SELECT ROUND(AVG(grade), 2) FROM grades)),
('最高成绩', (SELECT MAX(grade) FROM grades)),
('最低成绩', (SELECT MIN(grade) FROM grades));
-- 返回报告数据
SELECT * FROM temp_report;
-- 删除临时表
DROP TEMPORARY TABLE temp_report;
END //
DELIMITER ;
-- 调用存储过程
CALL sp_generate_grade_report();4. 创建查询存储过程
sql
-- 创建按条件查询学生的存储过程
DELIMITER //
CREATE PROCEDURE sp_search_students(
IN search_major VARCHAR(50),
IN search_class VARCHAR(50),
IN min_avg_grade DECIMAL(5,2)
)
BEGIN
SELECT
s.student_number AS 学号,
s.name AS 姓名,
s.major AS 专业,
s.class_name AS 班级,
ROUND(AVG(g.grade), 2) AS 平均分
FROM students s
LEFT JOIN grades g ON s.id = g.student_id
WHERE
(search_major IS NULL OR s.major = search_major) AND
(search_class IS NULL OR s.class_name = search_class)
GROUP BY s.id, s.student_number, s.name, s.major, s.class_name
HAVING
(min_avg_grade IS NULL OR AVG(g.grade) >= min_avg_grade) OR
(min_avg_grade IS NOT NULL AND AVG(g.grade) IS NULL AND min_avg_grade <= 0)
ORDER BY 平均分 DESC;
END //
DELIMITER ;
-- 调用存储过程查询计算机专业平均分80以上的学生
CALL sp_search_students('计算机科学与技术', NULL, 80);
-- 调用存储过程查询所有平均分75以上的学生
CALL sp_search_students(NULL, NULL, 75);小结
在本章中,我们学习了 MySQL 中的视图和存储过程:
- 视图 - 虚拟表,用于简化复杂查询和数据安全控制
- 存储过程 - 预编译的 SQL 语句集合,用于封装业务逻辑
- 存储函数 - 必须返回值的特殊存储程序
- 触发器 - 在特定事件发生时自动执行的程序
- 变量与控制结构 - 在存储过程中使用变量和控制流程
掌握这些高级数据库特性可以帮助我们:
- 简化复杂查询
- 提高查询性能
- 封装业务逻辑
- 增强数据安全性
- 实现自动化处理
在下一章中,我们将学习事务与并发控制,这是保证数据一致性和完整性的关键技术。