6.4 存储函数

#函数
/*
含义:一组预先编译好的SQL语句的集合,理解成批处理语句
1、提高代码的重用性
2、简化操作
3、减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率

存储过程与存储函数的区别对比:
存储过程:可以查询、可以增删改、可以不返回值
存储函数:必须 return 一个值(数字、字符串都行),适合处理数据后会返回一个结果的场景。

函数里不能写INSERT/UPDATE/DELETE(只读),适合做查询、计算、判断、拼接。
*/

#一、创建语法
sql
DELIMITER //
CREATE FUNCTION 函数名(参数)
RETURNS 返回类型--必须写!
BEGIN
    -- 逻辑代码
    RETURN 结果;  -- 必须返回!
END //
DELIMITER ;
/*

注意:
1.参数列表 包含两部分:
参数名 参数类型

2.函数体:肯定会有return语句,如果没有会报错
如果return语句没有放在函数体的最后也不报错,但不建议
存储函数必须返回一个值,必须指定DETERMINISTIC或NO SQL,否则 MySQL 5.7 会报错。
MySQL 5.7 强制要求你告诉它:
这个函数是不是只做查询、不修改数据。
你必须二选一:
DETERMINISTIC:输入固定 → 输出固定(查学生、算成绩、判断等级)
NO SQL:这个函数不操作数据库(纯计算)

代码里的体现:
RETURNS int  -- 必须声明返回什么类型
BEGIN
  RETURN 123;     -- 必须有 return 语句
END

3.存储函数没有输出变量,使用 into 变量名 来传递数值, 
SELECT 查出来多少个字段,INTO 就跟多少个变量,一一对应就行
标准格式:
SELECT 字段1, 字段2, 字段3 ...
INTO 变量1, 变量2, 变量3 ...
FROM 表
WHERE 条件;

一条 SELECT 只能有 一个 INTO    ❌不能写多个 INTO
查询字段个数 = INTO 后面变量个数,必须相等
顺序一一对应:第一个字段赋值给第一个变量,依次往下

4.函数体中仅有一句话,则可以省略begin end
5.使用 delimiter语句设置结束标记

*/

#二、调用语法
sql
SELECT 函数名(参数列表)
#案例
#一、创建函数,实现传入两个float,返回二者之和
sql
CREATE FUNCTION test_fun1(num1 FLOAT,num2 FLOAT) RETURNS FLOAT
BEGIN
	DECLARE SUM FLOAT DEFAULT 0;
	SET SUM=num1+num2;
	RETURN SUM;
END;
SELECT test_fun1(1,2);
#三、查看函数
sql
SHOW CREATE FUNCTION myf3;
#四、删除函数
sql
DROP FUNCTION myf3;
DROP FUNCTION IF EXISTS 函数名;
#------------------------------案例演示----------------------------
#1.无参有返回
#案例1:返回课程的个数
sql
CREATE FUNCTION myf1() RETURNS INT
BEGIN
	DECLARE c INT DEFAULT 0;  #定义局部变量
	SELECT COUNT(*) INTO c    #赋值
	FROM course;
	RETURN c;
END;
SELECT myf1();
#2.有参有返回
#案例2:创建存储函数myf2,输入学生姓名in_name,返回学号;查不到返回'未知'。
sql
DELIMITER //
CREATE FUNCTION myf2(in_name VARCHAR(10))
RETURNS VARCHAR(10)
BEGIN
    DECLARE n VARCHAR(10);
    SELECT Sno INTO n FROM student WHERE Sname = in_name LIMIT 1;
    IF n IS NULL THEN
        RETURN '未知';
    END IF;
    RETURN n;
END //
DELIMITER ;
-- 调用
sql
SELECT myf2('赵雷');
SELECT myf2('张三');
#案例3:创建存储函数myf3,输入学号 + 课程号,返回该生该课成绩;无成绩返回0。
sql
DELIMITER //
CREATE FUNCTION myf3(in_sno VARCHAR(10), in_cno VARCHAR(10))
RETURNS DECIMAL(18,1)
BEGIN
    DECLARE sc_val DECIMAL(18,1);
    SELECT score INTO sc_val FROM sc WHERE Sno = in_sno AND Cno = in_cno LIMIT 1;
    IF sc_val IS NULL THEN
        RETURN 0;
    END IF;
    RETURN sc_val;
END //
DELIMITER ;
-- 调用
sql
SELECT myf3('001','02');
拓展:创建存储函数myf3k,输入学号 + 课程号,返回该生的课程信息,课程信息为:"学生姓名/课程名/成绩"组成;如无姓名返回'无姓名',无课程返回'无课程',无成绩返回0。
sql
drop FUNCTION if EXISTS myf3k;
DELIMITER //
CREATE FUNCTION myf3k(in_sno VARCHAR(10), in_cno VARCHAR(10))
RETURNS VARCHAR(50)
BEGIN
    DECLARE xm VARCHAR(10) DEFAULT '无姓名';
    DECLARE kc VARCHAR(10) DEFAULT '无课程';
    DECLARE cj DECIMAL(5,1) DEFAULT 0;
    SELECT 
        IFNULL(student.sname, '无姓名'),
        IFNULL(course.cname, '无课程'),
        IFNULL(sc.score, 0)
    INTO xm, kc, cj  
		-- 一条 SELECT 语句,只能有 1 个 INTO,一个 INTO 后面,可以跟 N 个变量,没有数量限制 
    FROM student 
    JOIN sc ON student.Sno = sc.Sno 
    JOIN course ON course.Cno = sc.Cno 
    WHERE student.Sno = in_sno AND course.Cno = in_cno 
    LIMIT 1;

    RETURN CONCAT(xm, '/', kc, '/', cj);
END //
DELIMITER ;

-- 调用
sql
SELECT myf3k('001','02') as 课程信息;
-- 调用
sql
SELECT myf3k('101','102') as 课程信息;
第 4 题
创建存储函数fn_get_teacher_name,输入教师编号,返回教师姓名;查不到返回'无教师'。
sql
DELIMITER //
CREATE FUNCTION fn_get_teacher_name(in_tno VARCHAR(10))
RETURNS VARCHAR(10)
DETERMINISTIC
BEGIN
    DECLARE tname_val VARCHAR(10);
    SELECT Tname INTO tname_val FROM teacher WHERE Tno = in_tno LIMIT 1;
    IF tname_val IS NULL THEN
        RETURN '无教师';
    END IF;
    RETURN tname_val;
END //
DELIMITER ;
-- 调用
sql
SELECT fn_get_teacher_name('02');
第 5 题
创建存储函数fn_get_class_stu_count,输入班级号,返回该班学生人数。
sql
DELIMITER //
CREATE FUNCTION fn_get_class_stu_count(in_class CHAR(2))
RETURNS INT
DETERMINISTIC
BEGIN
    DECLARE cnt INT;
    SELECT COUNT(*) INTO cnt FROM student WHERE class = in_class;
    RETURN cnt;
END //
DELIMITER ;
-- 调用
sql
SELECT fn_get_class_stu_count('03');
第 6 题
创建存储函数fn_get_score_level,输入分数,按grades表规则返回:优秀 / 良好 / 及格 / 不及格。
sql
DELIMITER //
CREATE FUNCTION fn_get_score_level(score_val DECIMAL(18,1))
RETURNS VARCHAR(10)
DETERMINISTIC
BEGIN
    IF score_val >= 90 THEN RETURN '优秀';
    ELSEIF score_val >= 75 THEN RETURN '良好';
    ELSEIF score_val >= 60 THEN RETURN '及格';
    ELSE RETURN '不及格';
    END IF;
END //
DELIMITER ;
-- 调用
sql
SELECT fn_get_score_level(87);
第 7 题
创建存储函数fn_get_stu_total_score,输入学号,返回该生所有课程总分;无成绩返回0。
sql
DELIMITER //
CREATE FUNCTION fn_get_stu_total_score(in_sno VARCHAR(10))
RETURNS DECIMAL(18,1)
DETERMINISTIC
BEGIN
    DECLARE total DECIMAL(18,1);
    SELECT IFNULL(SUM(score),0) INTO total FROM sc WHERE Sno = in_sno;
    RETURN total;
END //
DELIMITER ;
-- 调用
sql
SELECT fn_get_stu_total_score('001');
第 8 题
创建存储函数fn_get_course_avg_score,输入课程号,返回该课平均分(保留 2 位);无成绩返回0。
sql
DELIMITER //
CREATE FUNCTION fn_get_course_avg_score(in_cno VARCHAR(10))
RETURNS DECIMAL(18,2)
DETERMINISTIC
BEGIN
    DECLARE avg_val DECIMAL(18,2);
    SELECT IFNULL(ROUND(AVG(score),2),0) INTO avg_val FROM sc WHERE Cno = in_cno;
    RETURN avg_val;
END //
DELIMITER ;
-- 调用
sql
SELECT fn_get_course_avg_score('01');
第 9 题
创建存储函数fn_is_pass,输入学号 + 课程号,返回'及格'或'不及格'。
sql
DELIMITER //
CREATE FUNCTION fn_is_pass(in_sno VARCHAR(10), in_cno VARCHAR(10))
RETURNS VARCHAR(10)
DETERMINISTIC
BEGIN
    DECLARE sc_val DECIMAL(18,1);
    SELECT score INTO sc_val FROM sc WHERE Sno = in_sno AND Cno = in_cno LIMIT 1;
    IF sc_val >= 60 THEN
        RETURN '及格';
    ELSE
        RETURN '不及格';
    END IF;
END //
DELIMITER ;
-- 调用
sql
SELECT fn_is_pass('004','01');
第 10 题(综合题)
创建存储函数fn_get_stu_score_info,输入学号,返回字符串:
姓名:XX,总分:XX,平均分:XX,等级:XX
平均分按总分 / 科目数算,等级按平均分判定。
sql
DELIMITER //
CREATE FUNCTION fn_get_stu_score_info(in_sno VARCHAR(10))
RETURNS VARCHAR(100)
DETERMINISTIC
BEGIN
    DECLARE s_name VARCHAR(10);
    DECLARE total_score DECIMAL(18,1);
    DECLARE cnt INT;
    DECLARE avg_score DECIMAL(18,2);
    DECLARE lev VARCHAR(10);
    DECLARE result VARCHAR(100);
    -- 姓名
    SELECT Sname INTO s_name FROM student WHERE Sno = in_sno LIMIT 1;
    -- 总分
    SELECT IFNULL(SUM(score),0) INTO total_score FROM sc WHERE Sno = in_sno;
    -- 科目数
    SELECT IFNULL(COUNT(*),0) INTO cnt FROM sc WHERE Sno = in_sno;
    -- 平均分
    IF cnt = 0 THEN
        SET avg_score = 0;
    ELSE
        SET avg_score = ROUND(total_score / cnt, 2);
    END IF;
    -- 等级
    SET lev = fn_get_score_level(avg_score);
    -- 拼接
    SET result = CONCAT('姓名:',s_name,',总分:',total_score,',平均分:',avg_score,',等级:',lev);
    RETURN result;
END //
DELIMITER ;

-- 调用
sql
SELECT fn_get_stu_score_info('001');