本文内容概要:

  1. UDF 概念、原理、优缺点、UDF 的分类

  2. 详细讲述3种 UDF 的创建、调用方法以及注意事项

  3. UDF 的实践建议

基本原理:

UDF:user-defined functions,用户自定义函数的简称。

UDF 是一个例程,它接受参数、执行操作并返回该操作的结果。根据定义,结果可以是标量值(单个)或表。

UDF 的优点:

  1. UDF 可以把复杂的逻辑嵌入到查询中。UDF 可以为复杂的表达式创建新函数。
  2. UDF 可以运用在一个表达式或 SELECT 语句的 FROM 子句中,并且还可以绑定到架构。此外,UDF 还可以接受参数。UDF 有助于实施一致性和可重用性。

UDF 的缺点:

该函数一旦误用会产生潜在的性能问题。必须针对WHERE子句的每一行执行的任何函数,不管是用户定义的函数还是系统函数,都将减慢执行速度。

UDF 的类型:

UDF 主要有 3 种类型(SQL Server Management Studio 把内联表值函数与多语句表值函数放到了一个组中):

  1. 标量函数
  2. 内联表值函数
  3. 多语句表值函数

一、标量函数


标量函数是返回一个具体值的函数。函数可以接收多个参数、执行计算然后返回一个值。返回值通过RETURN命令返回。用户定义的函数中的每个可能代码路径都以RETURN命令结尾。

标量函数可以运用于 SQL Server 中的任何表达式,甚至在 CHECK 约束的表达式中也可以使用(但不推荐这种用法)。

  • 函数限制

标量函数必须是确定性的,也就是说标量函数必须反复地为相同的输入参数返回相同的值。因此,如newid()函数和rand()函数不允许出现在标量函数中。不允许用户定义标量函数更新数据库、调用存储过程或调用DBCC命令,唯一的例外是可以更新表变量。用户定义函数不能返回BLOB(二进制大型对象)数据,如text、next、timestamp和image数据类型变量。也不能返回表变量可cursor数据类型。对于错误处理,UDF 也不包含 TRY...CATCH 或 RAISERROR。

UDF 可以调用嵌套深度为 32 层以内的其他用户定义函数,或者递归调用自己到 32 层的深度。当然,这只是理论限制,嵌套函数会严重影响性能,应尽可能避免使用嵌套函数。

  • 创建方法

 CREATE FUNCTION FunctionName (InputParameters)
RETURNS DataType
AS
BEGIN
Code;
RETURN Expression;
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 的实践建议

无疑 UDF 为我们的 T-SQL 选项添加了灵活性,但如果这些函数运用不当,带来的性能缺陷也是很严重的。UDF 并不能成为子查询、视图或存储过程的替代物。

从上面的示例,我们不难看出,三种类型函数可以产生基本相同的结果集,实践中可以将自己的函数定义为其种任意一种。

建议一:性能最优化

如果选择 UDF 来封装查询逻辑,则建议遵循下面的这些基本原则:

  1. 相对于多语句表值函数,尽可能优先选择内联表值函数;
  2. 尽量避免使用标量函数,尽可能使用内联表值函数取代它;
  3. 如果需要使用多语句表值函数,则对比一下存储过程是不是更合适的解决文案。虽然需要花更多的时间,但考虑长期的性能影响,还是值得的。

建议二:命名一致性

为方便我们的T-SQL更易于阅读更容易排除故障,我们应该确保为所有的 UDF 创建某种统一类型的命名约束。最常用的方法是采用名称前缀,更进一步,可以让前缀表明 UDF 是标量函数、内联表值函数还是多语句表值函数。例如,返回每个产品类别的月平均销售额的内联表值函数,可以将其命名为 udfAvgMonSalesPerCategory 或 ifn_AvgMonSalesPerCategory。

SQL SERVER 用户自定义函数(UDF)深入解析的更多相关文章

  1. SQL Server用户自定义函数

    用户自定义函数不能用于执行一系列改变数据库状态的操作,但它可以像系统 函数一样在查询或存储过程等的程序段中使用,也可以像存储过程一样通过EXECUTE 命令来执行.在 SQL Server 中根据函数 ...

  2. SQL Server用户自定义函数(UDF)

    一.UDF的定义 和存储过程很相似,用户自定义函数也是一组有序的T-SQL语句,UDF被预先优化和编译并且可以作为一个单元来进行调用. UDF和存储过程的主要区别在于返回结果的方式: 使用UDF时可传 ...

  3. SQL之用户自定义函数

    关于SQL Server用户自定义的函数,有标量函数.表值函数(内联表值函数.多语句表值函数)两种. 题外话,可能有部分朋友不知道SQL Serve用户自定义的函数应该是写在哪里,这里简单提示一下,在 ...

  4. Hive 文件格式 & Hive操作(外部表、内部表、区、桶、视图、索引、join用法、内置操作符与函数、复合类型、用户自定义函数UDF、查询优化和权限控制)

    本博文的主要内容如下: Hive文件存储格式 Hive 操作之表操作:创建外.内部表 Hive操作之表操作:表查询 Hive操作之表操作:数据加载 Hive操作之表操作:插入单表.插入多表 Hive语 ...

  5. sql server 自定义函数的使用

    sql server 自定义函数的使用 自定义函数 用户定义自定义函数像内置函数一样返回标量值,也可以将结果集用表格变量返回 用户自定义函数的类型: 标量函数:返回一个标量值 表格值函数{内联表格值函 ...

  6. SQL Server 自定义函数(Function)——参数默认值

    sql server 自定义函数分为三种类型:标量函数(Scalar Function).内嵌表值函数(Inline Function).多声明表值函数(Multi-Statement Functio ...

  7. SQL Server 聚合函数算法优化技巧

    Sql server聚合函数在实际工作中应对各种需求使用的还是很广泛的,对于聚合函数的优化自然也就成为了一个重点,一个程序优化的好不好直接决定了这个程序的声明周期.Sql server聚合函数对一组值 ...

  8. SQL Server排序函数row_number和rank的区别

    SQL Server排序函数row_number和rank的区别 直接看测试结果 declare @table table(name varchar(100),amount int, memo var ...

  9. 数据库开发基础-SQl Server 聚合函数、数学函数、字符串函数、时间日期函数

    SQL 拥有很多可用于计数和计算的内建函数. 函数的语法 内建 SQL 函数的语法是: SELECT function(列) FROM 表 函数的类型 在 SQL 中,基本的函数类型和种类有若干种.函 ...

随机推荐

  1. VMware workstation安装centos7,无ifconfig命令

    一.centos7默认未启动ifconfig,vi /etc/sysconfig/network-scripts/ifcfg-ens33,改成ONBOOT=yes: systemctl restart ...

  2. 有用的link

    资料 了解oi 刘汝佳代码仓库(紫书 c++参考手册 2018年洛谷日报索引 2019年洛谷日报索引 (其他oj: luogu 虚拟判官(名校oj都有 离线bzoj题库 (有时候进不去请点:rxz大爷 ...

  3. cookie、session、token的区别与联系

    https://www.cnblogs.com/moyand/p/9047978.html cookie.session.token存在意义 http协议是无状态协议,请求之间是没有联系的,cooki ...

  4. 使用Python对ElasticSearch获取数据及操作

    #!/usr/bin/env python# -*- coding: utf-8 -*-""" @Time : 2018/7/4 @Author : LiuXueWen ...

  5. JVM垃圾回收GC

    1.堆的分代和区域 (年轻代)Young Generation(eden.s0.s1  space)    Minor GC (老年代)Old Generation (Tenured space)   ...

  6. 【shell脚本】打印九九乘法表

    打印九九乘法表 一.seq介绍 seq命令用于以指定增量从首数开始打印数字到尾数,即产生从某个数到另外一个数之间的所有整数,并且可以对整数的格式.宽度.分割符号进行控制 语法: [1] seq [选项 ...

  7. CodeForce 577B Modulo Sum

    You are given a sequence of numbers a1, a2, ..., an, and a number m. Check if it is possible to choo ...

  8. VS 中批量格式化、删除未使用的 using 语句代码的插件

    插件名称:Format All Files 插件地址:https://marketplace.visualstudio.com/items?itemName=munyabe.FormatAllFile ...

  9. centos6 cgroup及cgred简介和简单使用

    一.cgroup简介 Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU.内存.磁盘输入输出等).这个项 ...

  10. Python platform 模块

    Python platform 模块 platform 模块用于查看当前操作系统的信息,来采集系统版本位数计算机类型名称内核等一系列信息. 使用方法: import platform # 获取操作系统 ...