日期:2014-03-05  浏览次数:20396 次

用户定义的函数 (UDF) 是准备好的代码片段,它可以接受参数,处理逻辑,然后返回某些数据。根据 SQL Server Books Online,SQL Server™ 2000 中的 UDF 可以接受从 0 到 1024 的任意个数的参数,不过我必须承认,我还未尝试将 1024 个参数传递到 UDF 中。UDF 的另一个关键特征是返回一个值。取决于 UDF 的类型,调用例程可以使用这个值来继续处理它的数据。因此,如果 UDF 返回单一值(标量值),调用例程就可以在任何能够使用标准变量或文字值的地方使用这个值。如果 UDF 返回一个行集,则调用例程可以循环访问该行集,联接到该行集,或简单地从该行集中选择列。iTbulo .comuLMyArz

虽然现在大多数编程语言已经暂时支持函数,但只有 SQL Server 2000 引入了 UDF。存储过程和视图在 SQL Server 中可用的时间远早于 UDF,但这些对象中的每一个在 SQL Server 开发中都有自己适当的位置。存储过程可以很好地用于处理复杂的 SQL 逻辑、保证和控制对数据的访问,以及将行集返回到调用例程,无论此例程是基于 Visual Basic® 的程序,还是另一个 Transact-SQL (T-SQL) 批处理文件。与视图不同,存储过程是已编译的,这使得它们成为用来表示和处理频繁运行的 SQL 语句的理想候选者。视图可以很好地用于控制对数据的访问,但它们的控制方式与存储过程不同。视图仅限于生成该视图的基础 SELECT 语句中的某些列和行。因而视图常用于表示常用的 SELECT 语句,该语句可以联接多个表、使用 WHERE 子句,以及公开特定的列。在联接到其他表和视图的 SQL 语句的 FROM 子句中经常会发现视图。iTbulo .comuLMyArz

在其核心部分,UDF 既类似于视图,也类似于存储过程。像视图一样,UDF 可以返回一个行集,该行集可用于 JOIN 中。因此,当 UDF 返回一个行集并接受参数时,它像一个您可以联接到的存储过程、或者一个参数化的视图。但是,正如我将演示的,UDF 可以做到这一点,甚至更多。iTbulo .comuLMyArz

有两种主要的 UDF 类型:返回标量值的 UDF 和返回表值的 UDF。在表值 UDF 中,您将找到返回内联表和多语句表的 UDF(请参见图 1)。在以下部分中,我将对每种类型都加以关注。iTbulo .comuLMyArz

标量 UDFiTbulo .comuLMyArz

返回标量值的 UDF 最类似于许多编程语言所引用的作为函数的内容。它们返回由标量数据类型(例如,integer、varchar(n)、char(n)、money、datetime、bit,等等)组成的单一值。如果用户定义的数据类型 (UDDT) 基于标量数据类型,UDF 也可以返回这些数据类型。使用返回内联或多语句表的 UDF,可以通过表数据类型返回行集。然而,并非所有的数据类型都可以从 UDF 中返回。例如,UDF 无法返回下列数据类型中任何一个的值:text、ntext、image、cursor、或 timestamp。iTbulo .comuLMyArz

返回标量数据类型的 UDF 可以用于多种情况,以使代码具有更好的可维护性、可重用性和更少的复杂性。当 T-SQL 代码的相同段在几个地方(可能由几个存储过程和批 SQL 语句)使用时,这会非常有用。例如,假定一个应用程序中的几个部分都需要查找产品是否必须重新订购。在每个需要此操作的地方,代码可以检查重新订购等级,并将它与库存量加订购量的和相比较。然而,因为这个代码在几个地方用到,所以可以改为使用 UDF 以减少代码块,并使得万一需要更改时维护函数更加容易。这样的 UDF 可能看起来像图 2 中的代码,并可以使用以下 SQL 语句进行调用: iTbulo .comuLMyArz

SELECT    ProductID,



ReorderLevel,



UnitsInStock,



UnitsOnOrder,



dbo.fnNeedToReorder(ReorderLevel, UnitsInStock, UnitsOnOrder)



AS sNeedToReorder



FROM    Products



图 2中,fnNeedToReorder UDF 执行计算并返回适当的值。这本来可以通过 CASE 语句在 SELECT 子句内完成,但如果改为使用 UDF,代码就会简洁得多。而且更容易传播到其他可能需要相同逻辑的地方。假定一个应用程序中有几个部分需要确定是否要重新订购产品,那么图 2 中的 UDF 确实变得有价值,因为它使得当逻辑改变时应用程序更容易维护。例如,重新订购已经终止的产品并不是很有意义。因此,通过更改 UDF 以说明这个业务规则,可以在一个地方更改此逻辑(请参见图 3)并使用下列代码运行: iTbulo .comuLMyArz

SELECT    ProductID,



ReorderLevel,



UnitsInStock,



UnitsOnOrder,



dbo.fnNeedToReorder(ReorderLevel, UnitsInStock, UnitsOnOrder,



Discontinued) AS sNeedToReorder



FROM    Products



请注意,UDF 是使用由两个部分(对象所有者和对象名)组成的名称调用的。当使用返回标量数据类型值的 UDF 时需要该对象的所有者。可以授权所有调用 UDF 的地方也必须加以更改,方法是将第四个参数 (Discontinued) 添加到 UDF 中。为了更容易维护,我可以重新编写 UDF,以便使用每一行的 ProductID 来检索数据本身,如图 4 所示。这种技术更容易维护,因为它不需要任何调用例程来更改逻辑改变时更改 UDF 的方式,只要可以从当前 Products 表行中提取数据即可。然而,要获得这种可维护性,会有性能方面的损失。图 4 中的 UDF 必须为每个从调用例程中返回的行从 Products 表中检索行。因为调用例程已经从 Products 表中检索每个行,所以如果该表有 77 行,则代码将执行 77 次 SELECT 语句(从主 SELECT 语句中返回每行一次)。虽然每个 SELECT 都是基于主键字段 (ProductID) 进行选择的,因而会很快,但是当行集非常大或者 SELECT 语句效率较低时,性能就会受到负面影响。图 4 中的代码可以通过以下 SQL 片段来调用: iTbulo .comuLMyArz

SELECT    ProductID,



ReorderLevel,



UnitsInStock,



UnitsOnOrder,



dbo.fnNeedToReorder(ProductId) AS sNeedToReorder



FROM    Products



在 SELECT 语句中使用这个函数的可选方法是,在名为 NeedToReorder 的 Products 表中创建一个计算所得的列。该列并不定义为一种数据类型,而是定义为如图 3 所示的 fnNeedToReorder UDF 的返回值。要添加此列,我可以按以下方式更改 Products 表,以指示应计算这个列: iTbulo .comuLMyArz

ALTER TABLE Products



ADD  NeedToReorder AS dbo.fnNeedToReorder(ReorderLevel,



UnitsInStock, UnitsOnOrder, Disc