MySQL 存储函数[特殊字符] VS 存储过程[特殊字符]

news2025/5/15 21:10:15

1、存储函数😸

一、存储函数概述

存储函数是MySQL中一种特殊的存储程序,具有以下特点:

  • 返回单个值:必须通过RETURN语句返回明确的结果
  • SQL表达式使用:可以直接在SQL语句中调用
  • 输入参数只接受输入参数(隐式IN),不支持OUT/INOUT参数
  • 确定性🤔🤔🤔:可声明为DETERMINISTIC(相同输入总是返回相同结果)

二、存储函数语法结构

DELIMITER //  -- 修改分隔符(可选)

CREATE FUNCTION 函数名(
    参数1 数据类型,
    参数2 数据类型,
    ...
)
RETURNS 返回值数据类型
[特性列表]
BEGIN
    -- 声明部分(DECLARE)
    -- 执行部分(SQL语句)
    -- 控制结构(IF/CASE/LOOP等)
    RETURN 返回值;  -- 必须包含
END //

DELIMITER ;  -- 恢复分隔符

三、参数与返回值

1. 输入参数(仅支持IN)

CREATE FUNCTION fn_calculate_tax(
    salary DECIMAL(10,2),
    tax_rate DECIMAL(5,2)
RETURNS DECIMAL(10,2)
BEGIN
    RETURN salary * tax_rate / 100;
END

2. 返回值类型

支持所有MySQL数据类型:

-- 返回字符串
CREATE FUNCTION fn_full_name(
    first_name VARCHAR(50),
    last_name VARCHAR(50))
RETURNS VARCHAR(100)
BEGIN
    RETURN CONCAT(first_name, ' ', last_name);
END

-- 返回日期
CREATE FUNCTION fn_next_monday()
RETURNS DATE
BEGIN
    RETURN DATE_ADD(CURDATE(), 
           INTERVAL (9 - IF(DAYOFWEEK(CURDATE())=1, 8, DAYOFWEEK(CURDATE()))) DAY);
END

四、变量与流程控制

1. 变量声明与使用

CREATE FUNCTION fn_age_category(age INT)
RETURNS VARCHAR(20)
BEGIN
    DECLARE category VARCHAR(20);
    
    IF age < 18 THEN
        SET category = '未成年';
    ELSEIF age BETWEEN 18 AND 60 THEN
        SET category = '成年';
    ELSE
        SET category = '老年';
    END IF;
    
    RETURN category;
END

2. 复杂逻辑示例

CREATE FUNCTION fn_inventory_status(
    quantity INT,
    threshold INT)
RETURNS VARCHAR(30)
BEGIN
    DECLARE status VARCHAR(30);
    
    CASE 
        WHEN quantity <= 0 THEN
            SET status = '缺货';
        WHEN quantity < threshold THEN
            SET status = '库存不足';
        ELSE
            SET status = '库存充足';
    END CASE;
    
    RETURN status;
END

五、确定性函数

CREATE FUNCTION fn_discount_price(
    original_price DECIMAL(10,2),
    discount DECIMAL(5,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
    RETURN original_price * (1 - discount/100);
END

六、存储函数调用

1. 在SQL中直接调用

-- 基本调用
SELECT fn_calculate_tax(5000, 15) AS tax_amount;

-- 与表数据结合使用
SELECT 
    employee_id,
    salary,
    fn_calculate_tax(salary, 20) AS tax
FROM employees;

-- 在WHERE条件中使用
SELECT *
FROM products
WHERE fn_inventory_status(quantity, 10) = '库存不足';

2. 在存储过程中调用

CREATE PROCEDURE sp_apply_raise(IN emp_id INT)
BEGIN
    UPDATE employees
    SET salary = salary * fn_get_raise_factor(department)
    WHERE id = emp_id;
END

七、管理存储函数

1. 查看函数

-- 查看所有函数
SHOW FUNCTION STATUS;

-- 查看函数定义
SHOW CREATE FUNCTION fn_name;

2. 修改函数

-- MySQL需要先删除再重建
DROP FUNCTION IF EXISTS fn_name;
CREATE FUNCTION fn_name() ...

3. 删除函数

DROP FUNCTION [IF EXISTS] fn_name;

八、最佳实践

  1. 命名规范:建议使用fn_前缀区分
  2. 单一职责:每个函数只完成一个明确功能
  3. 性能考虑:避免在频繁调用的函数中使用复杂查询
  4. 错误处理:考虑使用DECLARE HANDLER处理可能错误
  5. 注释:添加函数目的、参数和返回值说明

🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳

2、存储过程😸

一、存储过程概述

存储过程预编译的SQL语句集合,存储在数据库中,具有以下特点:

  • 预编译执行首次创建时编译,后续调用直接执行,效率高
  • 减少网络流量:客户端只需发送调用命令,而非多条SQL语句
  • 增强安全性:可限制用户对表的直接访问,通过过程间接操作
  • 代码复用:一次创建,多次调用,便于维护

二、存储过程语法结构

DELIMITER //  -- 修改分隔符(可选)

CREATE PROCEDURE 过程名(
    [IN|OUT|INOUT] 参数名 数据类型,
    ...
)
[特性列表]
BEGIN
    -- 声明部分(DECLARE)
    -- 执行部分(SQL语句)
    -- 控制结构(IF/CASE/LOOP等)
END //

DELIMITER ;  -- 恢复分隔符

三、参数类型详解

1. IN参数(默认)

CREATE PROCEDURE sp_raise_salary(
    IN emp_id INT,
    IN raise_amount DECIMAL(10,2)
)
BEGIN
    UPDATE employees 
    SET salary = salary + raise_amount
    WHERE id = emp_id;
END

2. OUT参数

CREATE PROCEDURE sp_get_employee_stats(
    OUT max_salary DECIMAL(10,2),
    OUT min_salary DECIMAL(10,2),
    OUT avg_salary DECIMAL(10,2)
)
BEGIN
    SELECT MAX(salary), MIN(salary), AVG(salary)
    INTO max_salary, min_salary, avg_salary
    FROM employees;
END

3. INOUT参数

CREATE PROCEDURE sp_increment_counter(
    INOUT counter INT,
    IN increment INT
)
BEGIN
    SET counter = counter + increment;
END

四、变量与流程控制

1. 变量声明与使用

CREATE PROCEDURE sp_calculate_bonus(IN emp_id INT)
BEGIN
    DECLARE base_salary DECIMAL(10,2);
    DECLARE years_service INT;
    DECLARE bonus DECIMAL(10,2);
    
    SELECT salary, DATEDIFF(NOW(), hire_date)/365 
    INTO base_salary, years_service
    FROM employees WHERE id = emp_id;
    
    SET bonus = base_salary * 0.1 * years_service;
    
    UPDATE employees 
    SET bonus = bonus 
    WHERE id = emp_id;
END

2. 条件控制(IF/CASE)

CREATE PROCEDURE sp_get_employee_level(IN emp_id INT, OUT level VARCHAR(20))
BEGIN
    DECLARE emp_salary DECIMAL(10,2);
    
    SELECT salary INTO emp_salary FROM employees WHERE id = emp_id;
    
    IF emp_salary > 10000 THEN
        SET level = '高级';
    ELSEIF emp_salary > 5000 THEN
        SET level = '中级';
    ELSE
        SET level = '初级';
    END IF;
END

3. 循环控制(WHILE/REPEAT/LOOP)

CREATE PROCEDURE sp_generate_test_data(IN num_rows INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    
    WHILE i <= num_rows DO
        INSERT INTO test_table VALUES(i, CONCAT('Item-', i));
        SET i = i + 1;
    END WHILE;
END

五、错误处理

CREATE PROCEDURE sp_transfer_funds(
    IN from_acc INT,
    IN to_acc INT,
    IN amount DECIMAL(10,2),
    OUT status VARCHAR(50)
)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET status = '转账失败';
    END;
    
    START TRANSACTION;
    
    UPDATE accounts SET balance = balance - amount 
    WHERE account_id = from_acc;
    
    UPDATE accounts SET balance = balance + amount 
    WHERE account_id = to_acc;
    
    COMMIT;
    SET status = '转账成功';
END

六、存储过程调用

1. 基本调用

-- 无参数
CALL sp_update_all_salaries();

-- IN参数
CALL sp_raise_salary(101, 500.00);

-- OUT参数
SET @max = 0, @min = 0, @avg = 0;
CALL sp_get_employee_stats(@max, @min, @avg);
SELECT @max, @min, @avg;

-- INOUT参数
SET @counter = 10;
CALL sp_increment_counter(@counter, 5);
SELECT @counter;  -- 输出15

2. 在应用程序中调用

// Java示例
try (Connection conn = DriverManager.getConnection(url, user, password);
     CallableStatement stmt = conn.prepareCall("{call sp_raise_salary(?, ?)}")) {
    
    stmt.setInt(1, 101);       // 设置第一个IN参数
    stmt.setBigDecimal(2, new BigDecimal("500.00"));  // 设置第二个IN参数
    stmt.execute();
}

七、管理存储过程

1. 查看存储过程

-- 查看所有存储过程
SHOW PROCEDURE STATUS;

-- 查看特定存储过程代码
SHOW CREATE PROCEDURE sp_name;

2. 修改存储过程

-- MySQL中需要先删除再重建
DROP PROCEDURE IF EXISTS sp_name;
CREATE PROCEDURE sp_name() ...

3. 删除存储过程

DROP PROCEDURE [IF EXISTS] sp_name;

八、最佳实践

  1. 命名规范:使用统一前缀如sp_proc_
  2. 参数验证:在过程开始验证输入参数有效性
  3. 注释:添加详细注释说明功能和参数
  4. 错误处理:为关键操作添加错误处理
  5. 避免长事务:长时间运行的过程应考虑分批次处理

3、存储函数😸 VS 存储过程😸

🔥 对比表

特性存储函数(FUNCTION)存储过程(PROCEDURE)
创建关键字CREATE FUNCTIONCREATE PROCEDURE
参数模式仅输入参数(隐式IN,不可写IN关键字)必须显式声明IN/OUT/INOUT
返回值必须用RETURNS声明类型且用RETURN返回值RETURNS,通过OUT参数或结果集返回数据
调用方式SELECT func_name()CALL proc_name()
能否执行DML通常只查询(非严格限制)可执行所有SQL(INSERT/UPDATE/DELETE等)

存储函数的正确语法

CREATE FUNCTION 函数名(
    参数1 数据类型,  -- 只能这样写,不能加IN/OUT!
    参数2 数据类型   -- 多个参数用逗号分隔
) 
RETURNS 返回值数据类型  -- 必须声明返回类型
[DETERMINISTIC|NOT DETERMINISTIC]  -- 可选特性
BEGIN
    -- 函数体
    RETURN;  -- 必须有RETURN语句
END;

示例:计算年龄总和

CREATE FUNCTION sum_ages(dept VARCHAR(50)) 
RETURNS INT
BEGIN
    DECLARE total INT;
    SELECT SUM(age) INTO total FROM Employee 
    WHERE department = dept;
    RETURN total;
END;

调用方式

-- 直接调用
SELECT sum_ages('Sales') AS department_total_age;

-- 与其他SQL语句结合使用
SELECT 
    department,
    sum_ages(department) AS total_age
FROM Employee
GROUP BY department;

存储过程的正确语法

CREATE PROCEDURE 过程名(
    [IN] 参数1 数据类型,   -- IN可省略(默认就是IN)
    OUT 参数2 数据类型,    -- 必须显式声明OUT
    INOUT 参数3 数据类型  -- 双向参数
)
BEGIN
    -- 过程体
    -- 可执行任意SQL,通过OUT参数返回值
END;

示例:获取最老员工信息

CREATE PROCEDURE get_oldest_employee(
    OUT emp_name VARCHAR(100),
    OUT emp_age INT
)
BEGIN
    SELECT name, age INTO emp_name, emp_age
    FROM Employee ORDER BY age DESC LIMIT 1;
END; 

调用方式

-- 第一步:声明用户变量接收OUT参数
SET @oldest_name = '';
SET @oldest_age = 0;

-- 第二步:调用存储过程
CALL get_oldest_employee(@oldest_name, @oldest_age);

-- 第三步:查看结果
SELECT @oldest_name AS oldest_employee_name, 
       @oldest_age AS oldest_employee_age;
       
--or 显示结果
SELECT CONCAT('最年长员工是: ', @name, ', 年龄: ', @age) AS result;

常见错误示范

  1. 在函数中使用OUT参数

    -- 错误!函数不允许OUT参数
    CREATE FUNCTION bad_example(OUT x INT)...
    
  2. 在过程中省略OUT声明

    -- 错误!参数默认是IN,不会输出值
    CREATE PROCEDURE bad_example(result INT)...
    
  3. 混淆调用方式

    -- 错误!函数不能用CALL
    CALL my_function();
    
    -- 错误!过程不能用SELECT
    SELECT my_procedure();
    

🌟 记忆口诀

函数数学公式输入→计算→返回单值
过程操作脚本输入/输出→执行多动作→无返回

4、答疑🤔

👆MySQL 存储函数的确定性(DETERMINISTIC)

确定性函数是指:对于相同的输入参数,函数总是返回完全相同的结果,不受外部因素影响。数学函数就是典型的确定性函数,例如:

-- 确定性函数示例
CREATE FUNCTION fn_square(x INT) 
RETURNS INT
DETERMINISTIC
BEGIN
    RETURN x * x;  -- 输入3永远返回9
END

非确定性函数则可能对相同的输入返回不同结果,例如:

-- 非确定性函数示例
CREATE FUNCTION fn_random_discount() 
RETURNS DECIMAL(3,2)
NOT DETERMINISTIC
BEGIN
    RETURN RAND() * 0.5;  -- 每次调用返回不同随机值
END

🤔为什么需要声明确定性?

  1. 查询优化。MySQL优化器会对确定性函数进行特殊处理:对于相同参数,可能缓存函数结果;在索引优化时有不同的处理策略。
  2. 复制环境要求。在主从复制环境中,非确定性函数可能导致数据不一致。
  3. 函数索引限制。只有确定性函数才能用于创建函数索引。

确定性场景

  • 数学计算(加减乘除、幂运算等)
  • 字符串处理(拼接、截取、格式化等)
  • 日期计算(基于固定公式的计算)
  • 业务规则计算(固定折扣率、税率等)

非确定性场景

  • 依赖当前时间(NOW(), CURDATE()
  • 使用随机数(RAND()
  • 调用外部服务(获取实时汇率、天气等)
  • 查询数据库(除非查询的表数据绝对不变)

😀特殊注意事项

  1. 默认行为
    如果不声明,MySQL默认认为函数是NOT DETERMINISTIC

  2. 错误声明的后果
    如果错误地将非确定性函数声明为确定性:
    -可能导致查询结果错误
    -在复制环境中造成数据不一致
    -函数索引返回不正确结果

  3. 时间函数的特殊处理

-- 虽然包含CURDATE(),但可以声明为DETERMINISTIC
-- 因为同一SQL语句中多次调用返回相同值
CREATE FUNCTION fn_is_birthday_today(birth_date DATE)
RETURNS BOOLEAN
DETERMINISTIC
BEGIN
    RETURN MONTH(birth_date) = MONTH(CURDATE()) 
       AND DAY(birth_date) = DAY(CURDATE());
END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法第十八天|530. 二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先

530. 二叉搜索树的最小绝对差 题目 思路与解法 第一想法&#xff1a; 一个二叉搜索树的最小绝对差&#xff0c;从根结点看&#xff0c;它的结点与它的最小差值一定出现在 左子树的最右结点&#xff08;左子树最大值&#xff09;和右子树的最左结点&#xff08;右子树的最小值…

微服务调试问题总结

本地环境调试。 启动本地微服务&#xff0c;使用公共nacos配置。利用如apifox进行本地代码调试解决调试问题。除必要的业务微服务依赖包需要下载到本地。使用mvn clean install -DskipTests进行安装启动前选择好profile环境进行启动&#xff0c;启动前记得mvn clean清理项目。…

美SEC主席:探索比特币上市证券交易所

作者/演讲者&#xff1a;美SEC主席Paul S. Atkins 编译&#xff1a;Liam 5月12日&#xff0c;由美国SEC加密货币特别工作组发起的主题为《资产上链&#xff1a;TradFi与DeFi的交汇点》系列圆桌会议如期举行。 会议期间&#xff0c;现任美SEC主席Paul S. Atkins发表了主旨演讲。…

MySQL Join连接算法深入解析

引言 在关系型数据库中&#xff0c;Join操作是实现多表数据关联查询的关键手段&#xff0c;直接影响查询性能和资源消耗。MySQL支持多种Join算法&#xff0c;包括经典的索引嵌套循环连接&#xff08;Index Nested-Loop Join&#xff09;、块嵌套循环连接&#xff08;Block Nes…

http请求卡顿

接口有时出现卡顿&#xff0c;而且抓包显示有时tcp目标机器没有响应&#xff0c; 但nginx和java应用又没有错误日志&#xff0c;让人抓耳挠腮&#xff0c;最终还是请运维大哥帮忙&#xff0c;一顿操作后系统暂时无卡顿了&#xff0c;佩服的同时感觉疑惑到底调整了啥东…

vite+vue建立前端工程

​ 参考 开始 | Vite 官方中文文档 VUE教程地址 https://cn.vuejs.org/tutorial/#step-1 第一个工程 https://blog.csdn.net/qq_35221977/article/details/137171497 脚本 chcp 65001 echo 建立vite工程 set PRO_NAMEmy-vue-appif not exist %PRO_NAME% (call npm i…

vue使用路由技术实现登录成功后跳转到首页

文章目录 一、概述二、使用步骤安装vue-router在src/router/index.js中创建路由器&#xff0c;并导出在vue应用实例中使用router声明router-view标签&#xff0c;展示组件内容 三、配置登录成功后跳转首页四、参考资料 一、概述 路由&#xff0c;决定从起点到终点的路径的进程…

day20-线性表(链表II)

一、调试器 1.1 gdb&#xff08;调试器&#xff09; 在程序指定位置停顿 1.1.1 一般调试 gcc直接编译生成的是发布版&#xff08;Release&#xff09; gcc -g //-g调式版本&#xff0c;&#xff08;体积大&#xff0c;内部有源码&#xff09;&#xff08;DeBug&#…

HTTP 连接复用机制详解

文章目录 HTTP 连接复用机制详解为什么需要连接复用&#xff1f;连接复用的实现方式HTTP/1.1 的 Keep-AliveHTTP/2 多路复用 HTTP/1.1 的队头阻塞问题 HTTP 连接复用机制详解 HTTP 连接复用是 HTTP/1.1 及更高版本中的核心优化机制&#xff0c;旨在减少 TCP 连接建立和关闭的开…

网络协议分析 实验六 TCP和端口扫描

文章目录 实验6.1 TCP(Transfer Control Protocol)练习二 利用仿真编辑器编辑并发送TCP数据包实验6.2 UDP端口扫描实验6.3 TCP端口扫描练习一 TCP SYN扫描练习二 TCP FIN扫描 实验6.1 TCP(Transfer Control Protocol) 建立&#xff1a;syn,syn ack,ack 数据传送&#xff1a;tcp…

Spring Web MVC————入门(2)

1&#xff0c;请求 我们接下来继续讲请求的部分&#xff0c;上期将过很多了&#xff0c;我们来给请求收个尾。 还记得Cookie和Seesion吗&#xff0c;我们在HTTP讲请求和响应报文的时候讲过&#xff0c;现在再给大家讲一遍&#xff0c;我们HTTP是无状态的协议&#xff0c;这次的…

每日算法-250514

每日算法学习记录 (2024-05-14) 今天记录三道 LeetCode 算法题的解题思路和代码。 1. 两数之和 题目截图: 解题思路 这道题要求我们从一个整数数组中找出两个数&#xff0c;使它们的和等于一个给定的目标值 target&#xff0c;并返回这两个数的下标。 核心思路是使用 哈希…

嵌入式培训之数据结构学习(三)gdb调试、单向链表练习、顺序表与链表对比

目录 一、gdb调试 &#xff08;一&#xff09;一般调试步骤与命令 &#xff08;二&#xff09;找段错误&#xff08;无下断点的地方&#xff09; &#xff08;三&#xff09;调试命令 二、单向链表练习 1、查找链表的中间结点&#xff08;用快慢指针&#xff09; 2、找出…

虚拟机安装CentOS7网络问题

虚拟机安装CentOS7网络问题 1. 存在的问题1.1 CentOS7详细信息 2. 解决问题3.Windows下配置桥接模式 1. 存在的问题 虽然已经成功在虚拟机上安装了CentOS7&#xff0c;但是依旧不能上网。 1.1 CentOS7详细信息 [fanzhencentos01 ~]$ hostnamectlStatic hostname: centos01Ic…

迅为RK3588开发板安卓GPIO调用APP运行测试

将网盘上的安卓工程文件复制到 Windows 电脑上。确保工程路径中使用英文字符&#xff0c;不包含中文。接着&#xff0c;启动 Android Studio&#xff0c;点击“Open”按钮选择应用工程文件夹&#xff0c;然后点击“OK”。由于下载 Gradle 和各种 Jar 包可能需要一段时间&#x…

Unity 红点系统

首先明确一个&#xff0c;即红点系统的数据结构是一颗树&#xff0c;并且红点的数据结构的初始化需要放在游戏的初始化中&#xff0c;之后再是对应的红点UI侧的注册&#xff0c;对应的红点UI在销毁时需要注销对红点UI的显示回调注册&#xff0c;但是不销毁数据侧的红点注册 - …

尼康VR镜头防抖模式NORMAL和ACTIVE的区别(私人笔记)

1. NORMAL 模式&#xff08;常规模式&#xff09; 适用场景&#xff1a;一般手持拍摄&#xff0c;比如人像、静物、风景或缓慢平移镜头&#xff08;如水平追拍&#xff09;等。工作特性&#xff1a; 补偿手抖引起的小幅度震动&#xff08;比如手持时自然的不稳&#xff09;&am…

从索引中排除 Elasticsearch 字段

作者&#xff1a;来自 Elastic Kofi Bartlett 说明如何配置 Elasticsearch 排除字段、为什么要这样做&#xff0c;以及应遵循的最佳实践。 更多阅读&#xff1a;Elasticsearch&#xff1a;inverted index&#xff0c;doc_values 及 source 想获得 Elastic 认证&#xff1f;了解…

超详细Docker教程

前言&#xff1a;大家在在Linux上部署mysql及其他软件时&#xff0c;大家想一想自己最大的感受是什么&#xff1f; 我相信&#xff0c;除了个别天赋异禀的人以外&#xff0c;大多数人都会有相同的感受&#xff0c;那就是麻烦。核心体现在三点&#xff1a; 命令太多了&#xff…

Java项目拷打(外卖+点评)

一、点评星球&#xff08;黑马点评&#xff09; 1、项目概述 1.1、项目简介 本项目是基于Spring Boot与Redis深度整合的前后端分离的点评平台。系统以Redis为核心技术支撑&#xff0c;重点解决高并发场景下的缓存穿透、击穿、雪崩等问题&#xff0c;涵盖商户展示、优惠券秒杀…