UDF 概念、原理、优缺点、UDF 的分类sql
详细讲述3种 UDF 的建立、调用方法以及注意事项数据库
UDF:user-defined functions,用户自定义函数的简称。架构
UDF 是一个例程,它接受参数、执行操做并返回该操做的结果。根据定义,结果能够是标量值(单个)或表。函数
该函数一旦误用会产生潜在的性能问题。必须针对WHERE子句的每一行执行的任何函数,不论是用户定义的函数仍是系统函数,都将减慢执行速度。性能
UDF 主要有 3 种类型(SQL Server Management Studio 把内联表值函数与多语句表值函数放到了一个组中):优化
标量函数是返回一个具体值的函数。函数能够接收多个参数、执行计算而后返回一个值。返回值经过RETURN命令返回。用户定义的函数中的每一个可能代码路径都以RETURN命令结尾。spa
标量函数能够运用于 SQL Server 中的任何表达式,甚至在 CHECK 约束的表达式中也可使用(但不推荐这种用法)。code
标量函数必须是肯定性的,也就是说标量函数必须反复地为相同的输入参数返回相同的值。所以,如newid()函数和rand()函数不容许出如今标量函数中。不容许用户定义标量函数更新数据库、调用存储过程或调用DBCC命令,惟一的例外是能够更新表变量。用户定义函数不能返回BLOB(二进制大型对象)数据,如text、next、timestamp和image数据类型变量。也不能返回表变量可cursor数据类型。对于错误处理,UDF 也不包含 TRY...CATCH 或 RAISERROR。对象
UDF 能够调用嵌套深度为 32 层之内的其余用户定义函数,或者递归调用本身到 32 层的深度。固然,这只是理论限制,嵌套函数会严重影响性能,应尽量避免使用嵌套函数。blog
1 CREATE FUNCTION FunctionName (InputParameters) 2 RETURNS DataType 3 AS 4 BEGIN 5 Code; 6 RETURN 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; GO select dbo.ufnCalculate(15.3 , 6.54), dbo.ufnCalculate(9.0 , DEFAULT); 结果: ------ ------ 2.38 9.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_DD DAYS_M ------ ------ 2019-01-31 31 2019-02-15 28
与视图类似,内联表值函数也是为一个存储的SELECT语句封装。内联表值函数保留了视图的优势,还添加了一些参数。
内联表值用户定义函数没有BEGIN / END主体。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_POS AS H --订单客户信息表 ON H.OS_NO=B.OS_NO WHERE H.CUS_NO=@custNo GROUP BY H.CUS_NO,B.PRD_NO ); GO
经过dbo.ufnGetProductTotalByCust查询客户代号为"CT060228" 的产品汇总数据,函数出如今SELECT语句的FROM部分:
SELECT PRD_NO,TOTAL_PRD FROM dbo.ufnGetProductTotalByCust('CT060228') ORDER BY PRD_NO DESC
返回结果(部分):
PRD_NO TOTAL_PRD ------------ ------------------ 10910030006 5792.00000000 10910040003 10776.00000000 10912060014 11442.00000000 10913040009 9276.00000000 11410030028 900.00000000 ......
与视图相比,内联表值函数的优点在于其可使用参数。而视图不包含参数,并且在运行时想要限制结果须要把 WHERE 子句添加到调用视图的 SELECT 语句中来实现。
示图的调用示例,假设已经存在视图 dbo.vwProductTotalByCust,调用视图时,在 SELECT 语句中添加了一个 WHERE 子句限制:
SELECT * FROM dbo.vwProductTotalByCust WHERE cus_no='CT060228'
表值用户定义函数的关联可使用 APPLY 命令,从而使 UDF 针对由主查询处理的每一行接受一个不一样的参数值。
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_NO NAME TOTAL_PRD ------------ ------------ ------------------------ 10910030006 3pcs storage jar 5792.00000000 10910040003 2pcs storage jar 10776.00000000 10912060014 4pcs spice jar 11442.00000000 10913040009 6pcs spice jar 9276.00000000 11410030028 salad dressing 900.00000000 ......
CROSS APPLY 的第2种形式是 OUTER APPLY 命令,操做上与左联接类似。这种形式下,主查询的行将包含在结果集中,而无论 UDF 返回的虚拟表是否为空。
标量函数和内联表值函数可生成完成相同的结果集,那么这二者的区别是什么呢?
标量函数针对每一行运行一次,而内联表值函数由查询优化器处理,很是相似于视图。由于内联表值函数会由查询优化器进行处理,因此建议尽量优先使用内联表值函数,而非标量函数。
架构绑定阻止更改或删除函数所依赖的任何对象。若是架构绑定函数引用了某个表A,那么表A不可更改或删除,但能够将列添加到表A。
架构绑定的方法:在函数建立语句的 RETURNS 以后和 AS 以前添加选项 WITH SCHEMA BINDING,以下所示:
1 CREATE FUNCTION FunctionName (InputParameters) 2 RETURNS DataType 3 WITH SCHEMA BINDING 4 AS 5 BEGIN 6 Code; 7 RETURN 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语句的FROM部分引用该函数,便可查询到函数的执行结果。下面的代码检索ufnGetProductsAndOrderTotals函数的结果:
SELECT ProductID,ProductName,TotalOrders FROM ufnGetProductsAndOrderTotals() ORDER BY TotalOrders DESC
结果集以下:
ProductID ProductName TotalOrders ------------ ------------------- -------------- 715 4 PCS Storage Jar 8311 780 6 PCS Spice Jar 6800 ......
无疑 UDF 为咱们的 T-SQL 选项添加了灵活性,但若是这些函数运用不当,带来的性能缺陷也是很严重的。UDF 并不能成为子查询、视图或存储过程的替代物。
从上面的示例,咱们不难看出,三种类型函数能够产生基本相同的结果集,实践中能够将本身的函数定义为其种任意一种。
若是选择 UDF 来封装查询逻辑,则建议遵循下面的这些基本原则:
为方便咱们的T-SQL更易于阅读更容易排除故障,咱们应该确保为全部的 UDF 建立某种统一类型的命名约束。最经常使用的方法是采用名称前缀,更进一步,可让前缀代表 UDF 是标量函数、内联表值函数仍是多语句表值函数。例如,返回每一个产品类别的月平均销售额的内联表值函数,能够将其命名为 udfAvgMonSalesPerCategory 或 ifn_AvgMonSalesPerCategory。