SQL|SQL SERVER 用户自定义函数(UDF)深入解析
本文内容概要:
- UDF 概念、原理、优缺点、UDF 的分类
- 详细讲述3种 UDF 的创建、调用方法以及注意事项
- UDF 的实践建议
UDF 是一个例程,它接受参数、执行操作并返回该操作的结果。根据定义,结果可以是标量值(单个)或表。
UDF 的优点:
- UDF 可以把复杂的逻辑嵌入到查询中。UDF 可以为复杂的表达式创建新函数。
- UDF 可以运用在一个表达式或 SELECT 语句的 FROM 子句中,并且还可以绑定到架构。此外,UDF 还可以接受参数。UDF 有助于实施一致性和可重用性。
UDF 的类型: UDF 主要有 3 种类型(SQL Server Management Studio 把内联表值函数与多语句表值函数放到了一个组中):
- 标量函数
- 内联表值函数
- 多语句表值函数
标量函数是返回一个具体值的函数。函数可以接收多个参数、执行计算然后返回一个值。返回值通过RETURN命令返回。用户定义的函数中的每个可能代码路径都以RETURN命令结尾。
标量函数可以运用于 SQL Server 中的任何表达式,甚至在 CHECK 约束的表达式中也可以使用(但不推荐这种用法)。
- 函数限制
UDF 可以调用嵌套深度为 32 层以内的其他用户定义函数,或者递归调用自己到 32 层的深度。当然,这只是理论限制,嵌套函数会严重影响性能,应尽可能避免使用嵌套函数。
- 创建方法
1 CREATE FUNCTION FunctionName (InputParameters) 2 RETURNS DataType 3 AS 4 BEGIN 5Code; 6RETURN Expression; 7 END;
InputParameters 输入参数包含数据类型定义。参数可以设置默认值(Parameter = default ),需要注意的是在 UDF 中有默认值的参数并不能成为可选参数,为在调用函数时请求到默认值,需要把关键字 DEFAULT 传递到函数的默认值参数位置。
示例1:下面的 UDF 执行一个简单的数学计算,其中第二个参数带有默认值。
CREATE FUNCTION dbo.ufnCalculate (@Numer_a numeric(5,2), @Numer_b numeric(5,2) = 1.0) RETURNS numeric(5,2) AS BEGIN RETURN @Numer_a / @Numer_b ; END; GOselect dbo.ufnCalculate(15.3 , 6.54), dbo.ufnCalculate(9.0 , DEFAULT); 结果: ------------ 2.389.00
示例2:计算并返回某个时间所在月份的天数。
CREATE FUNCTION [dbo].[GetMonthDay](@date datetime) RETURNS int AS BEGIN DECLARE @date1 datetime SELECT @date1 =Dateadd(MM,1,@date) RETURN day(Dateadd(DD,-day(@date1),@date1)) END;
- 调用方法
下面的脚本演示了在数据库的订单表中调用示例2中的函数及其返回值。
SELECT S.BIL_DD,dbo.GetMonthDay(BIL_DD) as DAYS_M FROM Orders S结果 BIL_DDDAYS_M ------------ 2019-01-3131 2019-02-1528
二、内联表值函数
与视图相似,内联表值函数也是为一个存储的SELECT语句封装。内联表值函数保留了视图的优点,还添加了一些参数。
- 创建方法
CREATE FUNCTION FunctionName (InputParameters) RETURNS Table AS RETURN (Select Statement);
示例:下面的示例返回某个客户所订购产品的汇总情况。
CREATE FUNCTION dbo.ufnGetProductTotalByCust (@custNo varchar (10)) RETURNS Table AS RETURN( SELECT H.CUS_NO,B.PRD_NO,SUM(B.QTY) as TOTAL_PRD FROM TF_POS AS B--订单货品明细表 LEFT JOIN MF_POSAS H --订单客户信息表 ON H.OS_NO=B.OS_NO WHERE H.CUS_NO=@custNo GROUP BY H.CUS_NO,B.PRD_NO ); GO
- 调用方法
SELECT PRD_NO,TOTAL_PRD FROM dbo.ufnGetProductTotalByCust('CT060228') ORDER BY PRD_NO DESC
返回结果(部分):
PRD_NOTOTAL_PRD
------------------------------
109100300065792.00000000
1091004000310776.00000000
1091206001411442.00000000
109130400099276.00000000
11410030028900.00000000
......
- 与视图的关系
示图的调用示例,假设已经存在视图 dbo.vwProductTotalByCust,调用视图时,在 SELECT 语句中添加了一个 WHERE 子句限制:
SELECT * FROM dbo.vwProductTotalByCust WHERE cus_no='CT060228'
- 关联方法
APPLY 命令具有两种形式。最普通的一种形式是 CROSS APPLY,它运行起来更像一个内联接。CROSS APPLY 命令联接主查询的数据与来自用户自定义函数的任意表值数据集。如果未从UDF 返回数据,那么主查询的行也不能返回,如下图的例子所示:
SELECT T.PRD_NO,P.NAME,T.TOTAL_PRD FROM PRDT P --产品资料表 CROSS APPLY dbo.ufnGetProductTotalByCust('CT060228')T ORDER BY T.PRD_NO DESC 结果:
PRD_NONAMETOTAL_PRD
------------------------------------------------
109100300063pcs storage jar5792.00000000
109100400032pcs storage jar10776.00000000
109120600144pcs spice jar11442.00000000
109130400096pcs spice jar9276.00000000
11410030028salad dressing900.00000000
......
CROSS APPLY 的第2种形式是 OUTER APPLY 命令,操作上与左联接相似。这种形式下,主查询的行将包含在结果集中,而不管 UDF 返回的虚拟表是否为空。
标量函数和内联表值函数可生成完成相同的结果集,那么这两者的区别是什么呢?
标量函数针对每一行运行一次,而内联表值函数由查询优化器处理,非常类似于视图。因为内联表值函数会由查询优化器进行处理,所以建议尽可能优先使用内联表值函数,而非标量函数。
- 架构绑定
架构绑定的方法:在函数创建语句的 RETURNS 之后和 AS 之前添加选项 WITH SCHEMA BINDING,如下所示:
1 CREATE FUNCTION FunctionName (InputParameters) 2 RETURNS DataType 3 WITH SCHEMA BINDING 4 AS 5 BEGIN 6Code; 7RETURN Expression; 8 END;
可以使用ALTER修改函数,使其不再包含架构绑定,以便可以修改引用对象。
三、多语句表值函数
将标量函数与内联表值函数的功能结合起来就构成了复杂的多语句表值函数。
特征:这种类型的函数创建了一个表变量,将它置于代码中,然后从函数返回,以便能在SELECT语句中使用。
优点:可以代码内生成复杂结果集,以便在SELECT语句中使用,在查询中构建复杂逻辑,并解决那些没有游标就很难解决的问题。
- 创建方法
CREATE FUNCTION FunctionName (InputParamenters) RETURNS @TableName TABLE (columns) AS BEGIN; Code to populate table variable RETURN; END;
示例:下面的过程构建了一个返回基本结果集的多语句表值用户定义的函数,函数首先在 CREATE FUNCTION 头中创建了一个名为 @PruductList 的表变量,在函数体中,两个 INSERT 语句置于@ProductList 表变更中,如果函数执行完毕,表变更 @ProductList 将作为函数的输出传回。
ufnGetProductsAndOrderTotals函数返回Product表中的每个产品和每个产品的订单总数。
CREATE FUNCTION ufnGetProductsAndOrderTotals() RETURNS @ProductList TABLE (ProductID int, ProductName nvarchar(100), TotalOrders int) AS BEGIN; INSERT @ProductList(ProductID,ProductName) SELECT ProductID,Name FROM Product; UPDATE p1 SET TotalOrders = (SELECT sum(sod.OrderQty) FROM @ProductList ip1 JOIN SalesOrderDetail sod ON ip1.ProductID = sod.ProductID WHERE ip1.ProductID = p1.ProductID) FROM @ProductList p1 ; RETURN; END;
- 调用方法
SELECT ProductID,ProductName,TotalOrders FROM ufnGetProductsAndOrderTotals() ORDER BY TotalOrders DESC
结果集如下:
ProductIDProductNameTotalOrders
---------------------------------------------
7154 PCS Storage Jar8311
7806 PCS Spice Jar6800
......
四、UDF 的实践建议
无疑 UDF 为我们的 T-SQL 选项添加了灵活性,但如果这些函数运用不当,带来的性能缺陷也是很严重的。UDF 并不能成为子查询、视图或存储过程的替代物。
从上面的示例,我们不难看出,三种类型函数可以产生基本相同的结果集,实践中可以将自己的函数定义为其种任意一种。
建议一:性能最优化 如果选择 UDF 来封装查询逻辑,则建议遵循下面的这些基本原则:
- 相对于多语句表值函数,尽可能优先选择内联表值函数;
- 尽量避免使用标量函数,尽可能使用内联表值函数取代它;
- 如果需要使用多语句表值函数,则对比一下存储过程是不是更合适的解决文案。虽然需要花更多的时间,但考虑长期的性能影响,还是值得的。
转载于:https://www.cnblogs.com/idreamo/p/10767923.html
推荐阅读
- gitlab|gitlab 通过备份还原 admin/runner 500 Internal Server Error
- 编写字典程序
- py连接mysql
- 2019-01-18Mysql中主机名的问题
- MySql数据库备份与恢复
- mysql|InnoDB数据页结构
- 数据库|SQL行转列方式优化查询性能实践
- mysql中视图事务索引与权限管理
- 精细化的思考
- MYSQL主从同步的实现