当前位置: 首页 > news >正文

MySQL 存储过程与函数:增强数据库功能

一、MySQL 存储过程与函数概述

(一)存储过程的定义与特点

存储过程是一组预编译的 SQL 语句集合,它们被存储在数据库中,可根据需要被重复调用。例如,在一个电商系统中,经常需要查询某个时间段内的订单数据,就可以将相关的查询语句封装成一个存储过程,后续每次有这样的查询需求时,直接调用该存储过程即可,无需重复编写 SQL 语句。

它具有多个显著特点。首先是预编译特性,存储过程在首次被创建时就进行编译,之后每次调用时无需再次编译,这相比于普通的 SQL 语句每次执行都要编译来说,极大地提高了执行效率,尤其在复杂查询或者频繁调用的场景下优势明显。

可重用性也是其重要特点之一,像上述电商系统里查询订单数据的存储过程,不同的业务模块或者不同的开发人员在需要获取该数据时都能直接调用,避免了代码的重复编写,提高了开发效率并且让代码的维护更加便捷。

另外,存储过程还能通过权限控制保障安全性。数据库管理员可以针对存储过程设置特定的访问权限,只允许有相应权限的用户或应用程序调用,这样即使对基础数据库表的访问权限进行严格限制,也能保证相关业务通过存储过程安全地操作数据库,比如限制某些用户不能直接修改订单表,但允许其调用特定的存储过程来进行经过审核的订单修改操作。

(二)函数的定义与特点

函数同样是存储在数据库中的编程单元,不过它是专为返回单一值而设计的。例如,在统计员工平均薪资的场景中,可以创建一个函数,传入员工薪资数据列,经过内部的计算逻辑后返回平均薪资这个单一的值。而且函数可以像普通表达式一样在 SQL 语句中直接使用,这使得 SQL 查询语句更加简洁明了。

它的存在提升了代码的可读性与可维护性。比如在多个不同的查询场景里都需要计算某个数据的绝对值,将绝对值的计算逻辑封装成一个函数后,每次使用时直接调用该函数就行,其他开发人员看到函数调用就能明白其目的,而如果业务逻辑发生变化,比如计算绝对值的规则改变了,只需要修改函数内部的实现代码,无需在每个使用的地方都去修改,大大降低了维护成本。

同时,函数也具有参数化的特点,在调用函数时可以传递不同的参数值,使其能够灵活处理各种数据情况,增强通用性,满足多样化的业务需求。例如一个字符串截取函数,可以根据传入的起始位置和截取长度参数,返回不同的字符串片段,方便在不同的数据处理场景中使用。

二、MySQL 存储过程的创建与使用

(一)创建存储过程的语法

在 MySQL 中,我们可以使用 CREATE PROCEDURE 语句来创建存储过程,其基本语法格式如下:

 

CREATE PROCEDURE procedure_name([IN | OUT | INOUT] param_name type[,...])

[characteristics...]

BEGIN

-- SQL语句集合

END;

其中,procedure_name 是存储过程的名称,可根据实际需求自行命名,但要尽量避免选取与 MySQL 内置函数相同的名称,以免发生错误。

参数部分,[IN | OUT | INOUT] param_name type 用于定义存储过程的参数。IN 表示输入参数,可用于向存储过程传递值;OUT 表示输出参数,用于存储过程返回操作结果;INOUT 则既可以充当输入参数也可以充当输出参数。例如,param_name 为参数名,type 可以是任何有效的 MySQL 数据类型,当有多个参数时,参数列表中彼此间用逗号分隔,当然存储过程也可以没有参数(此时存储过程的名称后仍需加上一对括号)。

characteristics 用于指定存储过程的特性,常见取值有:

  • LANGUAGE SQL:说明 BEGIN 和 END 之间的部分是由 SQL 语句组成的,当前系统支持的语言为 SQL,这也是 LANGUAGE 特性的唯一值。
  • [NOT] DETERMINISTIC:指明存储过程执行的结果是否确定。DETERMINISTIC 表示每次执行存储过程时,相同的输入会得到相同的输出;而 NOT DETERMINISTIC 表示相同的输入可能得到不同的输出,默认值为 NOT DETERMINISTIC。
  • {CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}:指明子程序使用 SQL 语句的限制。CONTAINS SQL 表明子程序包含 SQL 语句;NO SQL 表明子程序不包含 SQL 语句;READS SQL DATA 表明子程序包含读数据的语句;MODIFIES SQL DATA 表明子程序包含写数据的语句,默认为 CONTAINS SQL。
  • SQL SECURITY {DEFiner | INVOKER}:指明谁有权限来执行。DEFiner 表示只有定义者才能执行;INVOKER 表示拥有权限的调用者可以执行,默认值为 DEFiner。
  • COMMENT 'string':注释信息,可以用来描述存储过程。

例如,创建一个简单的无参数存储过程,用于查询 students 表中的所有数据,代码如下:

 

DELIMITER $$

CREATE PROCEDURE ShowAllStudents()

BEGIN

SELECT * FROM students;

END $$

DELIMITER ;

这里先使用 DELIMITER $$ 将语句结束符修改为 $$,是因为 MySQL 默认以分号作为语句结束标志,而存储过程体中可能包含多条 SQL 语句,如果仍以分号作为结束符,服务器在处理时会以遇到的第一条 SQL 语句结尾处的分号作为整个程序的结束符,就不会处理后面的语句了。存储过程定义结束后,再通过 DELIMITER ; 恢复默认的分号结束符。

再来看一个带有输入参数的存储过程示例,创建一个名为 GetStudentById 的存储过程,通过输入学生的 id 查询该学生的信息:

 

DELIMITER $$

CREATE PROCEDURE GetStudentById(IN student_id INT)

BEGIN

SELECT * FROM students WHERE id = student_id;

END $$

DELIMITER ;

还有包含输出参数的情况,比如创建一个存储过程 CountStudents,用于统计 students 表中的学生数量,并通过输出参数返回结果:

 

DELIMITER $$

CREATE PROCEDURE CountStudents(OUT student_count INT)

BEGIN

SELECT COUNT(*) INTO student_count FROM students;

END $$

DELIMITER ;

(二)存储过程中的逻辑构建

在存储过程主体(即 BEGIN 和 END 之间),可以包含各类 SQL 语句以及条件判断语句、循环语句等,以此来执行复杂的逻辑操作。

常见的 SQL 语句如 SELECT、INSERT、UPDATE、DELETE 都可以在存储过程中使用。例如,以下存储过程实现了向 employees 表中插入一条新员工记录的功能:

 

DELIMITER $$

CREATE PROCEDURE InsertEmployee(

IN emp_name VARCHAR(50),

IN emp_age INT,

IN emp_department VARCHAR(50)

)

BEGIN

INSERT INTO employees (name, age, department)

VALUES (emp_name, emp_age, emp_department);

END $$

DELIMITER ;

条件判断语句方面,IF 语句是常用的一种。比如根据员工的绩效分数来调整工资,代码示例如下:

 

DELIMITER $$

CREATE PROCEDURE AdjustSalary(IN emp_id INT, IN performance_score INT)

BEGIN

DECLARE current_salary DECIMAL(10, 2);

SELECT salary INTO current_salary FROM employees WHERE id = emp_id;

IF performance_score >= 90 THEN

UPDATE employees SET salary = current_salary * 1.2 WHERE id = emp_id;

ELSEIF performance_score >= 80 THEN

UPDATE employees SET salary = current_salary * 1.1 WHERE id = emp_id;

ELSE

UPDATE employees SET salary = current_salary * 0.9 WHERE id = emp_id;

END IF;

END $$

DELIMITER ;

这里先声明了一个变量 current_salary 用于存储当前员工的工资,然后根据传入的绩效分数进行不同程度的工资调整。

循环语句中,WHILE 循环也很常用。例如,下面的存储过程实现了计算从 1 累加到某个指定数字的和的功能:

 

DELIMITER $$

CREATE PROCEDURE CalculateSum(IN num INT, OUT sum_result INT)

BEGIN

DECLARE i INT DEFAULT 1;

DECLARE temp_sum INT DEFAULT 0;

WHILE i <= num DO

SET temp_sum = temp_sum + i;

SET i = i + 1;

END WHILE;

SET sum_result = temp_sum;

END $$

DELIMITER ;

在这个存储过程中,通过 WHILE 循环不断累加数字,最终将结果赋值给输出参数 sum_result。

(三)存储过程的调用方法

在 MySQL 中,使用 CALL 语句来调用已创建的存储过程。

对于无参数的存储过程,调用方式很简单。比如前面创建的 ShowAllStudents 存储过程,调用语句如下:

 

CALL ShowAllStudents();

这样就可以执行存储过程,查询并返回 students 表中的所有学生记录。

对于带有输入参数的存储过程,需要在调用时传入相应的参数值。例如调用 GetStudentById 存储过程,查询 id 为 5 的学生信息,代码如下:

 

CALL GetStudentById(5);

如果是包含输出参数的存储过程,在调用后还可以获取输出参数的值。以 CountStudents 存储过程为例,调用并获取学生数量的代码如下:

 

CALL CountStudents(@student_count);

SELECT @student_count;

这里先通过 CALL 语句执行存储过程,将统计得到的学生数量存储到用户变量 @student_count 中,然后再通过 SELECT 语句查询并显示这个变量的值。

再来看一个稍微复杂一点的调用示例,假设有一个存储过程 GetEmployeesByDepartment,它接受一个部门名称作为输入参数,返回该部门的所有员工信息:

 

DELIMITER $$

CREATE PROCEDURE GetEmployeesByDepartment(IN dept_name VARCHAR(50))

BEGIN

SELECT * FROM employees WHERE department = dept_name;

END $$

DELIMITER ;

调用这个存储过程查询 IT 部门的员工信息,代码如下:

 

CALL GetEmployeesByDepartment('IT');

(四)存储过程的修改与删除操作

  • 修改存储过程

使用 ALTER PROCEDURE 语句来修改存储过程,其语法格式如下:

 

ALTER PROCEDURE procedure_name [characteristics...]

其中,procedure_name 是要修改的存储过程的名称,characteristics 部分与创建存储过程时指定特性的取值类似,用于修改存储过程的相关特性。

例如,修改前面提到的 ShowAllStudents 存储过程的特性,将其访问数据的权限改为 MODIFIES SQL DATA,并指明调用者可以执行,代码如下:

 

ALTER PROCEDURE ShowAllStudents MODIFIES SQL DATA SQL SECURITY INVOKER;

需要注意的是,如果要修改存储过程的内容,一种常见的做法是先删除原存储过程,再以相同的命名创建新的存储过程;如果要修改存储过程的名称,也是先删除原存储过程,再以新的名称创建存储过程。

  • 删除存储过程

用 DROP PROCEDURE 语句来删除数据库中已经存在的存储过程,语法格式如下:

 

DROP PROCEDURE [IF EXISTS] procedure_name

IF EXISTS 关键字是可选的,指定这个关键字可以用于防止因删除不存在的存储过程而引发的错误。注意存储过程名称后面没有参数列表,也没有括号,而且在删除之前,必须确认该存储过程没有任何依赖关系,否则会导致其他与之关联的存储过程无法运行。

比如要删除 CountStudents 存储过程,代码如下:

 

DROP PROCEDURE CountStudents;

如果不确定存储过程是否存在,为了避免报错,可以这样写:

 

DROP PROCEDURE IF EXISTS CountStudents;

删除后,可以通过查询 information_schema 数据库下的 routines 表来确认删除是否成功,例如:

 

SELECT * FROM information_schema.routines WHERE routine_name = 'CountStudents';

如果查询结果为空集,就说明存储过程已经被成功删除了。

三、MySQL 函数的分类与使用示例

(一)数学函数

MySQL 提供了丰富的数学函数,方便我们在数据库操作中进行各类数值运算。

常见的数学函数如下:

  • ABS 函数:语法为ABS(N),其作用是返回一个数N的绝对值。例如,SELECT ABS(3.2) as val1, ABS(-12.87) as val2;,执行结果会返回两列数据,val1的值为3.2,val2的值为12.87,因为它分别计算了3.2和-12.87的绝对值。
  • FLOOR 函数:语法是FLOOR(X),它会返回不大于X的最大整数。比如SELECT FLOOR(2.5) as val1, FLOOR(-2.1) as val2;,查询结果里val1的值为2,val2的值为-3,即将2.5向下取整得到2,把-2.1向下取整得到-3。
  • CEIL 函数:其语法为CEIL(X),该函数返回不小于X的最小整数。像SELECT CEIL(2.5) as val1, CEIL(-2.1) as val2;语句,运行后val1的值是3,val2的值为-2,也就是对2.5向上取整为3,对-2.1向上取整为-2。

这些数学函数在实际应用中,可以帮助我们处理如计算数值的偏差、进行分页计算中的页码处理等多种与数值相关的场景,通过简单的函数调用就能完成复杂的数值逻辑处理,让数据库查询和操作更加高效便捷。

(二)字符串函数

在 MySQL 中,字符串函数对于处理文本数据起着至关重要的作用。

以下是一些常用的字符串函数:

  • CONCAT 函数:语法为CONCAT(str1, str2, …),它能够将输入的参数按照顺序连接成一个字符串。例如,SELECT CONCAT('11', '22', '33');,其结果会返回112233。需要注意的是,如果其中任何一个参数为NULL,则整个函数的返回值就为NULL,像SELECT CONCAT('11', '22', NULL);的执行结果就是NULL。
  • LOWER 函数:语法是LOWER(str),该函数会把输入的字符串str中的所有字符转换为小写形式。比如有一个表students,其中name字段存储着学生姓名,要查询所有学生姓名的小写形式,可以使用SELECT LOWER(name) FROM students;语句。
  • UPPER 函数:语法为UPPER(str),它的功能和LOWER函数相反,是将输入字符串str中的所有字符转换为大写形式。例如,SELECT UPPER('hello world');会返回HELLO WORLD。
  • TRIM 函数:用于去除字符串两端的空格(默认情况)或者指定的字符。语法如TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM] str),如果仅写TRIM(str),就是去除字符串str两端的空格,像SELECT TRIM(' hello ');会返回hello,去除了两端的空格。

通过这些字符串函数,我们可以灵活地对数据库中存储的文本数据进行格式化、拼接、大小写转换以及多余空格去除等操作,满足不同业务场景下对于字符串处理的需求。

(三)日期和时间函数

MySQL 有着诸多实用的日期和时间函数,方便我们在数据库层面进行与日期时间相关的数据处理。

  • CURDATE 函数:语法就是CURDATE(),它用于获取当前日期,返回的结果只包含年、月、日部分,格式为YYYY-MM-DD。例如,SELECT CURDATE();语句,可能返回类似2024-01-18这样的结果,这在我们需要按照当天日期筛选数据,或者记录某个操作发生的日期时非常有用。
  • CURTIME 函数:语法为CURTIME(),其作用是返回当前时间,仅包含时、分、秒部分,格式为HH:MM:SS。比如执行SELECT CURTIME();,可能得到11:27:44这样的时间值,适用于记录某个操作发生的具体时刻等场景。
  • NOW 函数:语法是NOW(),该函数能够返回当前日期和时间,包含年、月、日、时、分、秒,格式为YYYY-MM-DD HH:MM:SS。像SELECT NOW();语句,可能返回2024-01-18 13:30:45这样的结果,常用于需要同时记录操作发生的日期和时间的情况。
  • UNIX_TIMESTAMP 函数:语法为UNIX_TIMESTAMP(date),它可以将给定的日期时间值date转化为 UNIX 时间戳。例如,SELECT UNIX_TIMESTAMP(now());可以把当前的日期时间转换为对应的时间戳数值,方便在一些需要以时间戳形式存储或者比较时间的场景中使用,并且它也可以接收如CURDATE()等返回的日期值作为参数进行转换,如SELECT UNIX_TIMESTAMP(CURDATE());。

利用这些日期和时间函数,我们能够轻松地实现诸如查询某个时间段内的数据、统计按日或按时段的数据量等操作,为数据处理提供了有力的时间维度支持。

(四)系统信息函数

MySQL 的系统信息函数能够帮助我们获取数据库系统相关的各类有用信息。

  • VERSION 函数:语法就是VERSION(),此函数用于返回当前 MySQL 服务器的版本信息,它没有参数,直接调用即可。例如,SELECT VERSION();这条 SQL 语句将返回类似 “5.7.34” 这样的 MySQL 服务器版本号,有助于我们了解当前使用的 MySQL 服务器的版本,以便进行相应的操作和优化,同时也能根据版本差异来适配不同的功能或者解决兼容性问题。
  • CONNECTION_ID 函数:它可以获取当前连接的唯一标识符。语法为CONNECTION_ID(),在排查数据库连接相关问题,比如判断是否存在重复连接或者追踪特定连接的操作时,这个函数能提供关键的连接标识信息。
  • DATABASE 函数:语法是DATABASE(),该函数用于返回当前所在的数据库名称。例如,在一个复杂的数据库应用中,可能存在多个数据库切换操作的情况,通过SELECT DATABASE();就能快速确认当前处于哪个数据库下,确保后续的表操作等指向正确的数据库对象。
  • USER 函数:语法为USER(),其作用是返回当前登录的用户信息,包含用户名以及主机名等内容,方便进行权限管理和审计,了解是哪个用户在执行相应的数据库操作,像SELECT USER();就能获取到当前登录用户的详细信息。

这些系统信息函数为数据库的管理、监控以及应用开发过程中的环境判断等提供了重要的参考依据,让我们能更好地掌握数据库系统的状态和配置情况。

四、MySQL 存储过程与函数如何增强数据库功能

(一)性能方面的提升

在数据库的实际应用中,MySQL 存储过程与函数在性能方面有着显著的提升作用。

首先,存储过程与函数都是在数据库服务器端执行的。当客户端需要执行一系列数据库操作时,如果是采用普通的 SQL 语句发送方式,每一条 SQL 语句都要在客户端与服务器之间进行传输,然后服务器再执行,这样频繁的网络通信会带来较大的开销。而存储过程和函数将多条 SQL 语句封装在一起,只需客户端发起一次调用请求,就可以在服务器端直接执行这一组语句,大大减少了客户端与服务器之间的网络通信开销。

例如,在一个电商系统中,如果要查询某个用户的历史订单信息、计算订单总金额以及获取该用户的常用收货地址等多个操作,若使用普通 SQL 语句,可能需要多次向服务器发送不同的查询语句,网络传输的数据量较大。但如果将这些操作封装成一个存储过程,客户端只需调用这个存储过程,一次通信就能完成所有相关操作,效率明显提升。

另外,存储过程和函数的预编译特性也是提升性能的关键因素。它们在首次被创建时就进行了编译,之后每次调用时无需再次编译,直接执行编译好的二进制代码即可。与之相对的是,普通的 SQL 语句每次执行都要经历编译这个过程,尤其是在复杂查询或者频繁调用的场景下,存储过程和函数的这种优势就更为突出。比如一个用于统计网站每日活跃用户数量的函数,每天可能会被多次调用,如果每次调用都要编译对应的统计逻辑语句,会消耗大量时间和资源,而使用预编译好的函数则能快速返回结果,提高整个系统的响应速度。

(二)代码的可维护性与重用性

在软件开发与数据库管理中,代码的可维护性和重用性至关重要,而 MySQL 存储过程与函数很好地满足了这两方面的需求。

将常用的业务逻辑封装在存储过程和函数中,可以让代码的管理变得更加便捷,实现统一维护。例如,在一个企业资源管理系统(ERP)中,存在着计算员工薪资、统计部门绩效等常用业务逻辑。如果把计算员工薪资的逻辑分散在各个不同的模块代码里,当薪资计算规则发生变化,比如需要调整绩效奖金的计算方式或者增加某项补贴的计算时,就必须在每一处使用到该计算逻辑的地方去修改代码,这不仅工作量巨大,而且容易遗漏,导致出现数据不一致等问题。但要是把薪资计算的相关逻辑封装成一个存储过程,那么无论有多少个模块需要用到这个计算功能,只需要修改这一处存储过程里的代码,所有调用它的地方都会按照新的规则进行计算,真正做到一处修改多处受益。

同时,存储过程和函数具备很强的可重用性,它们可以被多个应用程序或者不同的业务逻辑重复调用。以一个电商系统为例,多个不同的页面可能都需要获取商品的库存信息,这时就可以创建一个获取商品库存的函数,无论是商品详情页面展示库存数量,还是购物车页面判断商品是否还有库存可购买,又或者是后台管理页面进行库存盘点等操作,都可以直接调用这个函数来获取库存信息,避免了重复编写相同的查询库存的代码,减少了代码冗余,提高了开发效率,也让整个代码结构更加清晰简洁。

(三)安全性保障

MySQL 存储过程在保障数据库安全性方面发挥着重要作用。

在很多实际应用场景中,数据库中存在着大量敏感数据,比如用户的个人信息(身份证号码、银行卡号等)、企业的财务数据等。如果直接让应用程序或者用户通过 SQL 语句去访问这些数据表进行操作,就可能存在数据泄露等安全风险。而存储过程可以用于封装这些涉及敏感操作的业务逻辑,用户只需要调用相应的存储过程,无需直接访问表。

例如,在一个银行系统中,对于用户账户余额的修改操作是非常敏感的。如果让外部应用直接执行类似 “UPDATE accounts SET balance = balance + 100 WHERE account_id = 12345” 这样的语句来修改余额,一旦应用端被黑客攻击或者权限管理出现漏洞,就可能导致任意账户余额被随意篡改。但要是将余额修改这个操作封装在一个存储过程里,如 “CREATE PROCEDURE UpdateBalance (IN account_id INT, IN amount INT) BEGIN UPDATE accounts SET balance = balance + amount WHERE account_id = account_id; END”,并且对这个存储过程设置严格的调用权限,只允许经过授权的内部模块或者特定用户角色去调用它,那么即使外部出现安全威胁,也能极大程度地保障账户余额数据的安全,因为攻击者无法直接操作数据表,只能按照规定好的存储过程调用方式来间接进行有限的操作,从而提高了整个数据库的安全性。

五、实践案例展示

(一)存储过程在数据归档与清理中的应用

在实际的数据库应用场景中,数据归档与清理是一项常见且重要的工作,而存储过程可以很好地帮助我们完成这类批量数据操作,下面以订单表数据按季度归档到历史表并删除源表对应数据为例进行展示。

首先,我们需要创建相应的存储过程,代码如下:

 

DELIMITER //

CREATE PROCEDURE ArchiveOrders()

BEGIN

DECLARE current_year INT;

DECLARE current_quarter INT;

-- 获取当前年份

SET current_year = YEAR(CURDATE());

-- 获取当前季度

SET current_quarter = QUARTER(CURDATE());

-- 复制当前季度的数据到历史表

INSERT INTO orders_history

SELECT *

FROM orders

WHERE YEAR(orderDate) = current_year AND QUARTER(orderDate) = current_quarter;

-- 删除源表中的数据

DELETE

FROM orders

WHERE YEAR(orderDate) = current_year AND QUARTER(orderDate) = current_quarter;

END //

DELIMITER ;

在上述代码中,先通过 YEAR(CURDATE()) 和 QUARTER(CURDATE()) 函数获取当前的年份和季度,然后使用 INSERT INTO...SELECT 语句将满足条件(即订单日期所在年份和季度与当前一致)的订单数据从 orders 表复制到 orders_history 表,最后再使用 DELETE 语句将源表 orders 中已归档的数据删除。

通过执行这个存储过程,我们可以在每个季度结束时方便地实现数据的自动归档和清理,无需手动去一条条筛选和处理数据,极大地提高了操作效率,同时也保证了数据的有效管理和存储,避免源表数据量过大而影响查询等性能。并且,如果后续业务规则发生变化,比如归档的时间周期、筛选条件等需要调整,只需要修改这个存储过程中的相应代码逻辑即可,非常便于维护。

(二)函数在业务数据计算中的应用

在业务系统中,常常需要根据不同的业务规则进行数据计算,函数就可以很好地帮我们实现这类逻辑,这里以产品销售系统中根据销量计算折扣为例来介绍。

我们可以创建如下的存储函数来实现根据销量计算折扣的业务逻辑:

 

DELIMITER $$

CREATE FUNCTION CalculateDiscount(total_sales INT) RETURNS DECIMAL(5,2) DETERMINISTIC

BEGIN

DECLARE discount DECIMAL(5,2);

IF total_sales >= 10000 THEN

SET discount = 0.20;

ELSEIF total_sales >= 5000 THEN

SET discount = 0.10;

ELSE

SET discount = 0.05;

ENDIF;

RETURN discount;

END $$

DELIMITER ;

在这个函数中,接收一个表示销量的参数 total_sales,然后根据其数值大小通过 IF 条件判断语句来确定对应的折扣值,最后将折扣值返回。

在查询中调用该函数获取结果也很简单,比如我们想查看每个产品的销量以及对应的折扣情况,可以这样写查询语句:

 

SELECT product_name, total_sales, CalculateDiscount(total_sales) AS discount

FROM products;

上述语句中,从 products 表中选取产品名称、销量等字段,并通过调用 CalculateDiscount 函数计算出对应的折扣,将其别名为 discount 展示出来。这样,利用函数我们可以将复杂的业务计算逻辑封装起来,在需要的地方方便地调用,使查询语句更加简洁明了,也便于根据业务变化对计算逻辑进行统一修改维护,很好地体现了函数在实际业务计算中的重要作用。

六、使用存储过程与函数的注意事项

(一)存储过程编写与维护难度

对于新手开发者来说,编写复杂的存储过程可能会面临一些挑战。存储过程中往往包含着较为复杂的业务逻辑,当逻辑变得复杂时,代码的可读性和可调试性就成了问题。比如在一个大型电商系统里,若要编写一个存储过程来处理包含多个条件判断、涉及多张数据表关联操作以及各种数据更新的订单处理逻辑,代码可能会变得很长且晦涩难懂,后续维护起来难度较大。

而且,一旦出现问题进行调试时,不像普通应用程序代码那样有比较成熟且直观的调试工具。不过,我们可以通过一些方法来缓解这些困难,例如合理添加注释,对关键的业务逻辑、复杂的条件判断和循环等部分都添加清晰易懂的注释,方便自己以及其他开发人员后续阅读和理解代码。同时,遵循良好的代码编写规范,保持代码结构清晰,变量命名做到见名知意等,也有助于提升存储过程的可维护性。

(二)存储过程的移植性问题

不同的数据库系统中,存储过程的语法和功能存在着差异,这就导致存储过程在进行移植时往往需要花费较大的改造工作量。比如在一些商用数据库中支持的特定函数或者语法结构,在开源数据库里可能并不支持,像世界著名的商用数据库对 SQL2003 的标准支持得很好,有较完整的窗口函数,而部分开源数据库可能连 FULL JOIN 都要转换成 UNION 来做,若存储过程中用到了这些不被支持的特性,移植时就需要重新设计算法来编写计算逻辑。

所以在开发项目时,如果考虑到后续可能有更换数据库或者项目需要跨数据库兼容等扩展性需求,那么在编写存储过程时就要尽量避免使用特定数据库独有的语法和功能,采用更通用的 SQL 语句及逻辑来实现业务需求,降低后期移植的成本。不过,如今传统关系数据库市场的竞争趋于稳定,各行业和业务采用的数据库基本定型,更换数据库的情况相对不那么频繁,但在开发之初做好规划依然是很有必要的。

(三)函数设计的副作用避免

在设计存储函数时,应当尽量避免使用那些会改变数据库状态的操作,例如 INSERT、UPDATE 或 DELETE 语句等。这是因为函数主要是为了进行一些计算并返回相应的结果,保持函数的 “纯净性”,也就是多执行只读操作是很重要的。如果函数中包含了改变数据库状态的操作,可能会产生意外的副作用。

比如,在一个统计员工平均薪资的函数中,它本应接收员工薪资数据列,经过内部的计算逻辑后返回平均薪资这个单一的值,倘若在这个函数里又去执行了更新某些员工薪资数据的操作,那当其他模块调用这个

相关文章:

MySQL 存储过程与函数:增强数据库功能

一、MySQL 存储过程与函数概述 &#xff08;一&#xff09;存储过程的定义与特点 存储过程是一组预编译的 SQL 语句集合&#xff0c;它们被存储在数据库中&#xff0c;可根据需要被重复调用。例如&#xff0c;在一个电商系统中&#xff0c;经常需要查询某个时间段内的订单数据…...

丹摩|丹摩助力selenium实现大麦网抢票

丹摩&#xff5c;丹摩助力selenium实现大麦网抢票 声明&#xff1a;非广告&#xff0c;为用户体验 1.引言 在人工智能飞速发展的今天&#xff0c;丹摩智算平台&#xff08;DAMODEL&#xff09;以其卓越的AI算力服务脱颖而出&#xff0c;为开发者提供了一个简化AI开发流程的强…...

springcloud-gateway获取应用响应信息乱码

客户端通过springcloud gateway跳转访问tongweb上的应用&#xff0c;接口响应信息乱码。使用postman直接访问tongweb上的应用&#xff0c;响应信息显示正常。 用户gateway中自定义了实现GlobalFilter的Filter类&#xff0c;在该类中获取了上游应用接口的响应信息&#xff0c;直…...

Scala项目(一)

1&#xff0c;创建dao&#xff0c;models&#xff0c;service&#xff0c;ui等软件包 2&#xff0c;在各软件包下创建scala类 软件包dao里的代码 package org.app package daoimport models.BookModelimport scala.collection.mutable.ListBuffer//图书&#xff0c;数据操作…...

node(2) - npm run 原理

1. npm run 执行原理 npm run 命令的原理是执行 package.json 文件中定义的脚本。当你在命令行中运行 npm run 时,npm 会查找 package.json 文件中的 scripts 字段,然后执行对应的脚本命令。 2. 示例 2.1 以 dev:weapp 为例 运行 npm run dev:weapp 命令;npm 会查找 packa…...

概率论得学习和整理24:EXCEL的各种图形,统计图形

目录 0 EXCEL的各种图形&#xff0c;统计图形 1 统计图形 / 直方图 / 其实叫 频度图 hist最合适(用原始数据直接作图) 1.1 什么是频度图 1.2 如何创建频度图&#xff0c;一般是只选中1列数据&#xff08;1个数组&#xff09; 1.3 如何修改频度图的宽度 1.4 hist图的一个特…...

【zlm】 webrtc源码讲解三(总结)

目录 setsdp onwrite ​编辑 play 参考 setsdp onwrite play 参考 【zlm】 webrtc源码讲解_zlm webrtc-CSDN博客 【zlm】 webrtc源码讲解&#xff08;二&#xff09;_webrtc 源码-CSDN博客...

YashanDB共享集群产品能力观测:细节足见功底

本文基于前泽塔数科研发总监-王若楠2024年11月在“2024年国产数据库创新生态大会”-“根”技术专场的演讲整理形成&#xff0c;主要对崖山共享集群YAC的架构、功能、高可用性、性能四大方面进行全面测试&#xff0c;并分享了测试环境和测试结论。 年初&#xff0c;基于某些商业…...

游戏引擎学习第50天

仓库: https://gitee.com/mrxiao_com/2d_game Minkowski 这个算法有点懵逼 回顾 基本上&#xff0c;现在我们所处的阶段是&#xff0c;回顾最初的代码&#xff0c;我们正在讨论我们希望在引擎中实现的所有功能。我们正在做的版本是初步的、粗略的版本&#xff0c;涵盖我们认…...

前端部署实战:从人工发布到全自动化流程

"又发错环境了&#xff01;"周四下午,测试同学小李急匆匆地找到我。原来是开发人员手动部署时,不小心把测试代码发布到了生产环境。这已经是本月第二次类似的事故了。 回想起每次发布时的场景&#xff1a;手动打包、手动上传、手动替换文件...每一步都战战兢兢,生怕…...

JVM系列之内存区域

每日禅语 有一位年轻和尚&#xff0c;一心求道&#xff0c;多年苦修参禅&#xff0c;但一直没有开悟。有一天&#xff0c;他打听到深山中有一古寺&#xff0c;住持和尚修炼圆通&#xff0c;是得道高僧。于是&#xff0c;年轻和尚打点行装&#xff0c;跋山涉水&#xff0c;千辛万…...

如何用3个月零基础入门网络安全?_网络安全零基础怎么学习

&#x1f91f; 基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 前 言 写这篇教程的初衷是很多朋友都想了解如何入门/转行网络安全&#xff0c;实现自己的“黑客梦”。文章的宗旨是&#xff1a; 1.指出一些自学的误区 2.提供…...

易语言OCR证件照文字识别

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…...

【人工智能】基于Python的自然语言处理:深入实现文本相似度计算

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 文本相似度计算是自然语言处理(NLP)中的核心任务,广泛应用于搜索引擎、推荐系统、问答系统等领域。本文全面解析文本相似度计算的核心技术,使用Python中的spaCy和sentence-transformers库实现多种方法,包括基…...

基于wifi的火焰报警系统设计(论文+源码)

1 总体方案设计 在本次基于wifi模板的火焰报警系统中&#xff0c;整个系统架构如图2.1所示&#xff0c;其采用STM32F103单片机作为控制器&#xff0c;并结合DS18B20温度传感器,火焰传感器&#xff0c;ESP8266 WiFi通信模块&#xff0c;蜂鸣器,OLED液晶构成整个系统&#xff0c;…...

【第三节】Git 基本操作指南

目录 前言 一、获取与创建项目 1.1 git init 1.2 git clone 二、基本快照操作 2.1 git add 2.2 git status 2.3 git diff 2.4 git commit 2.5 git reset HEAD 三、 文件管理 3.1 git rm 3.2 git mv 四、Git 文件状态 5.1 工作目录 5.2 暂存区 5.3 本地仓库 5…...

GaLore和Q-GaLore:一种记忆高效的预训练和微调策略,用于大型语言模型(LLMs)

GaLore和Q-GaLore&#xff1a;一种记忆高效的预训练和微调策略&#xff0c;用于大型语言模型&#xff08;LLMs&#xff09; GaLore和Q-GaLore的设计背景、工作原理及其优势 设计背景 随着大型语言模型&#xff08;LLMs&#xff09;的发展&#xff0c;模型的规模和复杂性不断…...

免费开源了一个图床工具 github-spring-boot-starter

文章目录 第一步&#xff0c;新建一个SpringBoot项目第二步&#xff0c;在pom文件里面引入jar包第三步&#xff0c;配置你的github信息github.authorization1、进入github官网&#xff0c;登录账号&#xff0c;点击头像&#xff0c;选择setting2、选择[Developer Settings](htt…...

Android显示系统(13)- 向SurfaceFlinger提交Buffer

Android显示系统&#xff08;01&#xff09;- 架构分析 Android显示系统&#xff08;02&#xff09;- OpenGL ES - 概述 Android显示系统&#xff08;03&#xff09;- OpenGL ES - GLSurfaceView的使用 Android显示系统&#xff08;04&#xff09;- OpenGL ES - Shader绘制三角…...

python小课堂(一)

基础语法 1 常量和表达式2 变量和类型2.1 变量是什么2.2 变量语法 3 变量的类型3.1 动态类型特性 4 注释4.1注释是什么 5 输入输出5.1 print的介绍5.2 input 6 运算符6.1 算术运算符在这里插入图片描述6.2 关系运算符6.3 逻辑运算符6.4赋值运算符 1 常量和表达式 在print()中可…...

【原创教程】西门子1500TCP_UDP通信说明大全(下篇)

2.3.3 TRCV故障说明 通讯无法正常连接时,ERROR引脚和STATUS引脚得状态有助于我们判断错误得原因,根据下表得提示,快速排除问题。 2.3.4 TRCV使用 点击TRCV指令得右上角蓝色图标,打开开始组态画面,按照控制要求填写 EN_R:用于激活接收的控制参数,及何时使用TRCV的接收功…...

【报错记录】Ubuntu22.04解决开机卡在 /dev/sda5 : clean , *files , *blocks

一个愿意伫立在巨人肩膀上的农民...... 一、错误现象 本人的电脑安装Windows10和Ubuntu22.04双系统&#xff0c;一次训练中电脑死机无法开机&#xff0c;重启之后便出现如下错误&#xff0c;在网上寻找过很多方法均无效&#xff0c;在root下禁用了samba服务&#xff0c;也无济…...

JumpServer开源堡垒机搭建及使用

目录 一,产品介绍 二,功能介绍 三,系统架构 3.1 应用架构 3.2 组件说明 3.3 逻辑架构 3.3 逻辑架构 四,linux单机部署及方式选择 4.1 操作系统要求(JumpServer-v3系列版本) 4.1.1 数据库 4.1.3创建数据库参考 4.2 在线安装 4.2.1 环境访问 4.3 基于docker容…...

libilibi项目总结(17)Elasticsearch 的使用

这段代码定义了一个 EsSearchComponent 类&#xff0c;主要用于与 Elasticsearch 进行交互&#xff0c;执行一些基本的操作&#xff0c;如创建索引、保存、更新和删除文档&#xff0c;及搜索操作。以下是对每部分代码的详细解释&#xff1a; 1. 类的依赖注入 Resource privat…...

C++ 模版函数 函数模版 区别

C中&#xff0c;函数模板&#xff08;Function Template&#xff09;和模板函数&#xff08;Template Function&#xff09;是同一个概念&#xff0c;通常没有区分&#xff0c;但为了避免混淆&#xff0c;有时我们可以从不同的角度来看待它们。 1. 函数模板 (Function Templat…...

SpringBoot 3.4.x踩坑记录及解决方案(持续更新)

废话 最近使用JDK17Spring Boot3.4.0 做新项目遇到的一些坑&#xff0c;记录并且给出一些实际的解决方案 一、集成Mybatis Plus 3.5.9的问题 第一&#xff1a;不能只引入mybatis-plus-spring-boot3-starter依赖了&#xff0c;需要配合mybatis-plus-jsqlparser <dependenc…...

Linux文件属性 --- 七种文件类型---文件.目录、软硬链接、字符设备文件

目录 七种文件类型 1、普通文件和目录 2、链接文件 2.1硬链接 2.2软链接 3、字符设备文件 一、七种文件类型 Linux的文件属性中一共有以下七种类型 &#xff1a; 符号类型含义解释-普通文件纯文本文件&#xff08;ASCII&#xff09;和二进制文件&#xff08;binary&#xff…...

C# 读取EXCEL的数据批量插入单个PDF里的多个位置

C# 读取EXCEL的数据批量插入单个PDF里的多个位置 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; usin…...

ARM Linux 虚拟环境搭建

一、目标 在没有arm硬件的情况下&#xff0c;使用QEMU模拟器&#xff0c;在PC上模拟一块ARM开发板&#xff0c;对ARM Linux进行学习。 二、搭建步骤 首先先有一个Linux 开发环境&#xff0c;我目前使用的是Ubuntu20. 首先安装qemu&#xff0c;qemu的官网&#xff1a;https:…...

【功能安全】安全确认

目录 01 功能安全确认介绍 02 安全确认用例 03 安全确认模板 01 功能安全确认介绍 定义: 来源...

LruCache(本地cache)生产环境中遇到的问题及改进

问题&#xff1a;单机qps增加时请求摘要后端&#xff0c;耗时也会增加&#xff0c;因为超过了后端处理能力&#xff08;最大qps&#xff0c;存在任务堆积&#xff09;。 版本一 引入LruCache。为了避免数据失效&#xff0c;cache数据的时效性要小于摘要后端物料的更新时间&…...

【21天学习AI底层概念】day8 什么是类意识?

类意识&#xff08;Quasi-Consciousness&#xff09; 是一个用来描述人工智能或复杂系统表现出的类似意识的行为或特性的概念。虽然这种系统不具备真正的意识&#xff08;即主观体验、情感和自我觉知&#xff09;&#xff0c;但在外部表现上&#xff0c;它们可能表现出与有意识…...

PostgreSQL JSON/JSONB 查询与操作指南

PostgreSQL 提供了强大的 JSON 和 JSONB 数据类型及相关操作&#xff0c;适用于存储和查询半结构化数据。本文将详细介绍其常用操作。 1. 基础操作 1.1 JSON 属性访问 ->: 返回 JSON 对象中的值&#xff0c;结果为 JSON 格式。 SELECT {"a": {"b": 1…...

SamOutV2 0.18B模型发布

项目地址 SamOutV2 0.18B模型 采取 em参数共享在参数量减半的情况下将维度从1024 拉升到了1536sft 单论对话 loss 保持1.8如果未来匹配state 推理代码性能不变的同时推理任意长度使用资源空间保持不变 模型代码 import torchclass MaxState(torch.nn.Module):def __init__(…...

〔 MySQL 〕事务管理

事务代码目录 1. 设置事务隔离级别 2. 开启事务 3. CRUD操作 3.1 创建&#xff08;Create&#xff09; 3.2 读取&#xff08;Read&#xff09; 3.3 更新&#xff08;Update&#xff09; 3.4 删除&#xff08;Delete&#xff09; 4. 提交或回滚事务 5. 示例&#xff1a…...

centOS定时任务-cron服务

最近在训练模型的过程中&#xff0c;经常会因为内存爆炸而停止模型训练过程&#xff0c;而且因为内存占满停止的训练进程甚至都没有任何的报错提示。 1、需要减少num_worker的数量&#xff0c;降低需要占用内存的数据数量 2、可以通过free -h监控内存的占用情况 3、可以通过lin…...

ubuntu22.04.5本地apt源部署

很多情况下&#xff0c;内网服务器无法连接互联网&#xff0c;这样如果原始系统只是最基本的下载安装包&#xff0c;因为存在依赖包不全的情况&#xff0c;难以对其进行更新及通过apt安装包 所以为解决不能联网的问题&#xff0c;首先先通过可以联网的机器制造好源&#xff0c;…...

CSS 实现带tooltip的slider

现代 CSS 强大的令人难以置信 这次我们来用 CSS 实现一个全功能的滑动输入器&#xff0c;也就是各大组件库都有的slider&#xff0c;效果如下 还可以改变一下样式&#xff0c;像这样 特别是在拖动时&#xff0c;tooltip还能跟随拖动的方向和速度呈现不同的倾斜角度&#xff0c…...

【LeetCode每日一题】——220.存在重复元素 III

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时空频度】九【代码实现】十【提交结果】 一【题目类别】 数组 二【题目难度】 困难 三【题目编号】 220.存在重复元素 III 四【题目描述】 给你一个…...

Git命令

目录 一、创建版本库 二、pwd 命令是用于显示当前的目录 三、通过命令 git init 把这个目录变成git可以管理的仓库 四、ll 五、添加文件和修改提交文件 1.创建文件test.txt --- 此刻文件在工作区&#xff08;WorkSpace&#xff09; 2.使用命令 git add test.txt添加到暂…...

2024第十六届蓝桥杯模拟赛(第二期)-Python

# 2024第十六届蓝桥杯模拟赛&#xff08;第二期&#xff09;-Python题解 # 自己改注释# -----------------------1------------------------ # def prime(x): # if x < 2: # return 0 # for i in range(2, int(x ** 0.5) 1): # if x % i 0: # …...

数据结构:Win32 API详解

目录 一.Win32 API的介绍 二.控制台程序(Console)与COORD 1..控制台程序(Console): 2.控制台窗口坐标COORD&#xff1a; 3.GetStdHandle函数&#xff1a; &#xff08;1&#xff09;语法&#xff1a; &#xff08;2&#xff09;参数&#xff1a; 4.GetConsoleCursorInf…...

Hive-4.0.1数据库搭建(可选配置用户名密码远程连接,涵盖切换为tez引擎)

一、hive搭建&#xff08;所依赖的Hadoop集群参照文章&#xff1a;最新版hadoop-3.4.0集群安装和配置&#xff08;目前论坛的都是老古董了&#xff0c;看我的准没错&#xff01;&#xff01;&#xff01;&#xff09;这里以三台服务器为例_hadoop 3.4安装-CSDN博客&#xff09;…...

【从零开始入门unity游戏开发之——C#篇13】命名规范——驼峰命名法和帕斯卡命名法,函数(方法)的使用介绍

文章目录 一、命名规范1、**驼峰命名法&#xff08;Camel Case&#xff09;**用途&#xff1a; 2、**帕斯卡命名法&#xff08;Pascal Case&#xff09;**用途&#xff1a; 3、**C# 中命名约定的最佳实践**3.1 **类、结构体、接口、枚举、委托**3.2 **方法、属性、事件**3.3 **…...

Android 写排行榜,顶部前三

activity_step_rank.xml <?xml version"1.0" encoding"UTF-8"?> <FrameLayout android:layout_height"match_parent" android:layout_width"match_parent" android:id"id/fragment_parent" android:orientation…...

sql server一些冷知识

1. Sql Server冷知识 &#xff08;1&#xff09; 删除表内容的方法 truncate table 表名 &#xff08;清除表记录&#xff0c;这个快&#xff09; &#xff08;2&#xff09; 列出所有数据库 sp_redatabases &#xff08;3&#xff09; 存储过程的参数命名 参数一定要以开头&am…...

【功能安全】随机硬件失效导致违背安全目标的评估(FMEDA)

目录 01 随机硬件失效介绍 02 FMEDA介绍 03 FMEDA模板 01 随机硬件失效介绍 GBT 34590 part5...

【Qt】信号、槽

目录 一、信号和槽的基本概念 二、connect函数&#xff1a;关联信号和槽 例子&#xff1a; 三、自定义信号和槽 1.自定义槽函数 2.自定义信号函数 例子&#xff1a; 四、带参的信号和槽 例子&#xff1a; 五、Q_OBJECT宏 六、断开信号和槽的连接 例子&#xff1a; …...

二叉树、平衡二叉树、红黑树、BTree、B+Tree的区别

一、二叉查找树 二叉树具有以下性质&#xff1a;左子树的键值小于根的键值&#xff0c;右子树的键值大于根的键值。 如下图所示就是一棵二叉查找树&#xff0c; 对该二叉树的节点进行查找发现深度为1的节点的查找次数为1&#xff0c;深度为2的查找次数为2&#xff0c;深度为n…...

【Rust自学】3.1. 变量与可变性

3.1.0. 写在正文之前 欢迎来到Rust自学的第三章&#xff0c;一共有6个小节&#xff0c;分别是: 变量与可变性&#xff08;本文&#xff09;数据类型&#xff1a;标量类型数据类型&#xff1a;复合类型函数和注释控制流&#xff1a;if else控制流&#xff1a;循环 通过第二章…...