在MySQL中,函数主要分为两大类:内置函数和用户自定义函数(UDF)。
内置函数
MySQL提供了丰富的内置函数来帮助执行各种操作,比如数学运算、字符串处理、日期时间处理等。使用这些函数非常直接,你只需要按照正确的语法在你的SQL查询中调用它们即可。例如:
- 数学函数:
ABS(-5)
返回 5。 - 字符串函数:
UPPER('hello')
返回 'HELLO'。 - 日期函数:
CURDATE()
返回当前日期。
用户自定义函数(UDF)
如果你需要的功能不能通过内置函数实现,你可以创建自己的函数。这通常涉及到存储过程或函数的编写。这里我们关注于如何创建一个简单的标量函数,它可以接受输入参数并返回一个结果。
一、MySQL 函数的本质
在 MySQL 中,函数(Function) 是一种可重复使用的、带有逻辑的数据库对象,它的核心特点是:
- 必须返回一个值 :这是函数与存储过程最根本的区别。函数通过
RETURN
语句返回一个结果。 - 可以接受参数 :函数可以定义输入参数(
IN
参数,这是默认的)。 - 可以在 SQL 语句中调用 :函数最强大的地方在于它能像
SUM()
,UPPER()
这样的内置函数一样,被嵌入到SELECT
,WHERE
,INSERT
,UPDATE
等 SQL 语句中使用。 - 有确定性(Deterministic)属性 :如果函数对于相同的输入总是返回相同的结果,可以声明为
DETERMINISTIC
,这有助于查询优化器进行优化。
二、函数的基本语法结构
sql
DELIMITER $$
CREATE FUNCTION function_name (parameter1 datatype, parameter2 datatype, ...)
RETURNS return_datatype
[DETERMINISTIC | NOT DETERMINISTIC] -- 可选,是否确定性
READS SQL DATA | MODIFIES SQL DATA | CONTAINS SQL | NO SQL -- 可选,数据访问特性
BEGIN
-- 函数体:声明变量、控制流、SQL 语句等
DECLARE variable_name datatype DEFAULT value;
-- ... 逻辑处理 ...
RETURN return_value; -- 必须有,且返回值类型与声明一致
END$$
DELIMITER ;
三、对例子进行详细解析
1. 最简单的函数:f_hello()
sql
CREATE FUNCTION `f_hello`() RETURNS varchar(100) CHARSET utf8mb3
BEGIN
RETURN 'Hello World';
END
- 特点 :
- 无参数。
- 返回一个固定的字符串
varchar(100)
。 - 可以直接在
SELECT
中调用:SELECT f_hello();
-> 输出Hello World
。
- 用途:演示基本语法,或作为占位符。
2. 带计算逻辑的函数:f_jiecheng(n)
sql
CREATE FUNCTION `f_jiecheng`(n int) RETURNS bigint
BEGIN
declare result bigint DEFAULT 1;
declare i int default 1;
WHILE i <= n DO
IF i % 7 <> 0 THEN -- 注意:这里是排除 7 的倍数
SET result = result * i;
END IF;
SET i = i + 1;
END WHILE;
RETURN result;
END
- 特点 :
- 接受一个
INT
类型的参数n
。 - 使用
DECLARE
声明了局部变量result
和i
。 - 包含了循环 (
WHILE
) 和条件判断 (IF
)。 - 返回一个
BIGINT
类型的计算结果。
- 接受一个
- 调用 :
SELECT f_jiecheng(5);
计算的是1*2*3*4*5
(因为 7>5,所以不影响),返回120
。 - 注意:这个函数计算的不是标准阶乘(排除了 7 的倍数),但逻辑清晰。
3. 带 SQL 查询和游标的函数:f_get_avg_study_point(classid)
sql
CREATE FUNCTION `f_get_avg_study_point`(classid int) RETURNS float
BEGIN
DECLARE endLoop INT DEFAULT 0;
DECLARE point int DEFAULT 0;
DECLARE sum int DEFAULT 0;
DECLARE amount int;
DECLARE cur CURSOR FOR SELECT study_points from t_students where class_id = classid;
DECLARE CONTINUE HANDLER FOR NOT FOUND set endLoop = 1;
OPEN cur;
FETCH cur INTO point;
WHILE endLoop < 1 do
SET sum = sum + point;
FETCH cur INTO point;
END WHILE;
CLOSE cur;
SELECT count(*) INTO amount from t_students where class_id = classid;
RETURN ROUND(sum/amount,2);
END
- 特点 :
- 接受班级 ID 作为参数。
- 使用 游标(CURSOR) 遍历
t_students
表中指定班级的学生学分。 - 使用
DECLARE CONTINUE HANDLER FOR NOT FOUND
来捕获游标遍历结束的信号(NOT FOUND
),并设置标志endLoop=1
来退出循环。 - 手动累加学分 (
sum
),再查询学生总数 (amount
),最后计算平均值。 - 使用
ROUND()
函数四舍五入到 2 位小数。
4. 嵌套查询和条件判断的函数:f_get_level(id)
sql
CREATE FUNCTION `f_get_level`(id int) RETURNS char(2) CHARSET utf8mb4 COLLATE utf8mb4_general_ci
BEGIN
DECLARE avg_iq float;
DECLARE current_iq int;
-- 查询该学生所在班级的平均智商
SELECT avg(iq) INTO avg_iq FROM t_student where class_id = (
SELECT class_id from t_student t where t.id = id
);
-- 查询该学生的智商
SELECT iq INTO current_iq from t_student t1 where t1.id = id;
IF current_iq > avg_iq THEN
RETURN '及格';
ELSE
RETURN '淘汰';
END IF;
END
- 特点 :
- 接受学生 ID。
- 包含子查询:先查出该学生所属的
class_id
,再用这个class_id
去查全班的平均智商。 - 使用
SELECT ... INTO variable
将查询结果赋值给局部变量。 - 使用
IF...ELSE...END IF
进行条件判断。 - 返回一个
CHAR(2)
类型的字符串('及格' 或 '淘汰')。
- 调用 :
SELECT f_get_level(101);
返回学生 ID 为 101 的等级。 - 优化思考:这个查询可以优化为一次连接查询,但作为函数逻辑是清晰的。
5.存储过程 p_delete_student()
- 基于函数判断的删除操作
- 核心逻辑 :
- 使用游标
cur
遍历t_student
表中所有学生 ID (pid
)。 - 对每个
pid
,调用函数f_get_level(pid)
来判断该学生是否"淘汰"。 - 如果函数返回
'淘汰'
,则执行DELETE
语句删除该学生记录。
- 使用游标
- 关键点 :
- 函数调用 :
SELECT f_get_level(pid) INTO a;
这是函数在过程内部被调用的典型方式。函数f_get_level
的返回值被赋给了变量a
。 - DML 操作 :过程内部执行了
DELETE
语句,这只能 在存储过程中实现,不能在函数中实现。 - 游标与循环 :展示了如何使用游标和
WHILE
循环来逐行处理查询结果。 - 错误处理 :
DECLARE CONTINUE HANDLER FOR NOT FOUND SET con = 1;
处理了游标FETCH
时没有更多数据的情况,这是游标编程的标准做法。
- 函数调用 :
- 为什么是过程? 因为它执行了数据修改(
DELETE
),并且没有单一的返回值,而是执行一系列动作。
综合梳理:MySQL 函数与存储过程的核心要点
特性/方面 | 函数 (FUNCTION) | 存储过程 (PROCEDURE) |
---|---|---|
定义关键字 | CREATE FUNCTION |
CREATE PROCEDURE |
返回值 | 必须 有 RETURNS 子句和 RETURN 语句。返回单一值。 |
没有 RETURNS 子句。可通过 OUT /INOUT 参数返回多个值,或不返回。 |
调用方式 | 可在 SQL 表达式中 调用:SELECT func_name(...), column FROM table; 或 WHERE func_name(...) = value; |
使用 CALL proc_name(...); 语句独立调用。 |
参数类型 | 默认且仅支持 IN 参数(输入)。 |
支持 IN (输入), OUT (输出), INOUT (输入输出)。 |
DML 操作 | 禁止 执行 INSERT , UPDATE , DELETE 等修改数据的语句。 |
允许 执行任何 DML 语句。 |
事务控制 | 不能包含 COMMIT 或 ROLLBACK 。 |
可以包含 COMMIT 或 ROLLBACK 来显式管理事务。 |
主要用途 | 数据计算、转换、格式化、复杂条件判断(返回结果)。作为 SQL 语句的一部分。 | 执行复杂的业务逻辑、批处理任务、数据清理、报表生成、调用多个函数/过程等。 |
你的例子 | f_hello , f_jiecheng , f_get_avg_study_point , f_get_level , f_sum_unil |
p_delete_student , proc_gen_student_score |
函数调用 | 可在自身或其他过程/函数中被调用(需注意限制)。 | 可在自身或其他过程/函数中被调用(用 CALL )。 |
错误处理 | 可使用 DECLARE ... HANDLER 。 |
可使用 DECLARE ... HANDLER 。 |
游标 | 可以 使用游标(如 f_get_avg_study_point ),但不能在游标循环内执行 DML。 |
可以 使用游标,并在循环内执行 DML(如 p_delete_student , proc_gen_student_score )。 |
游标(Cursor) 在数据库管理系统中是一种用于遍历查询结果集的数据库对象。它允许应用程序或用户逐行处理查询返回的数据,而不是一次性将所有数据加载到内存中。这对于处理大量数据或者需要对每一行执行特定操作的情况特别有用。
游标的基本概念
- 声明游标:定义一个游标,并将其与一个查询关联起来。
- 打开游标:执行与游标关联的查询,并准备好获取数据。
- 提取数据:从查询结果集中逐行获取数据。
- 关闭游标:完成数据处理后,关闭游标释放资源。
MySQL中的游标使用
在MySQL中,游标通常在存储过程或函数内部使用。以下是一个基本的游标操作流程:
sql
-- 声明变量
DECLARE var_name datatype;
-- 声明游标
DECLARE cur_name CURSOR FOR select_statement;
-- 声明继续处理器,当SQL语句没有找到更多行时设置标志位
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- 打开游标
OPEN cur_name;
-- 循环提取数据
read_loop: LOOP
FETCH cur_name INTO var_name;
IF done THEN
LEAVE read_loop;
END IF;
-- 处理逻辑...
END LOOP;
-- 关闭游标
CLOSE cur_name;
关键点说明
- DECLARE CURSOR :定义游标并指定与其关联的
SELECT
语句。 - OPEN:执行游标对应的查询,并使游标指向结果集的第一行之前。
- FETCH:将当前行的数据提取到指定的变量中,并将游标向前移动到下一行。
- CONTINUE HANDLER :定义了当没有更多行可供读取时的行为(例如设置一个标志变量
done
为1
)。 - CLOSE:关闭游标,释放相关资源。