SQL Server 2005 用户自定义函数

(《SQL Server 2005 编程入门经典》 第13章)

用户自定义函数和存储过程非常相似,但它们也有一些行为和能力的区别。

13.1 UDF的定义

用户自定义函数是有序的T-SQL语句集合,该语句集合能够预先优化和编译,并且可以作为一个单元来调用。它和存储过程的主要区别在于返回结果的方式。为了能支持多种不同的返回值,UDF比存储过程有更多地限制。

可以在使用存储过程的时候传入参数,也可以以参数的形式得到返回值。存储过程可以返回值,不过该值是为了指示成功或失败的,而非返回数据。

然而,可以在使用UDF的时候传入参数,但是可以不传出任何值。UDF还可以返回标量(scalar)值,这个值可以是大部分SQL Server的数据类型。UDF还可以返回表。

按照返回值的类型,UDF有两种类型:

  • 返回标量的UDF
  • 返回表的UDF

创建UDF的基本语法:

CREATE FUNCTION [<schema name>.]<function name>

    ([<@parameer name> [AS] [<schema name>.]<scalar data type>

        [= <default value>]

    [, ...n]])

RETURNS {<scalar type>|TABLE [(<table definition>)]}

    [WITH [ENCRYPTION]|[SCHEMABINDING]|

    [RETURNS NOLL ON NULL INPUT|CALLED ON NULL INPUT]|

    [EXECUTE AS {CALLER|SELF|OWNER|<'user name'>}]]

[AS] {EXTERNAL NAME <external method name>

BEGIN

    [<function statements>]

    {RETURN <type as defined in RESURNS clause>|RETURN

    (<select statement>)}

END }[;]

这里一个很大的问题在于是返回一个标量数据类型还是表,是使用基于T-SQL的函数还是使用CLR和.NET。

13.2 返回标量值得UDF

这种类型的UDF和大多数SQL Server内建的函数一样,会向调用脚本或存储过程返回标量值,例如GETDATE()和USER()函数就会返回标量值。

UDF可以返回除了BLOB、CURSOR和TIMESTAMP以外的任何SQL Server中有效的数据类型(包含用户自定义类型)。如果想返回整数,UDF也和存储过程不同的是:

  • UDF返回值的目的是提供有意义的数据,而不是说明成功或失败。
  • 在查询中可以内联地执行函数,而使用存储过程则不行。

示例——返回去掉时分秒的日期:

CREATE FUNCTION DayOnly(@Date DATETIME)

RETURNS VARCHAR(12)

AS

BEGIN

    RETURN CONVERT(VARCHAR(12), @Date, 101)

END

函数的使用方法如下:

SELECT *

FROM Orders

WHERE DayOnly(OrderDate) = DayOnly(GETDATE())

在一个UDF中调用另一个UDF:

CREATE FUNCTION AveragePrice()

RETURNS MONEY

WITH SCHEMABINDING

AS

BEGIN

    RETURN (SELECT AVG(Price) FROM Titles)

END

GO

 

CREATE FUNCTION PriceDifference(@Price MONEY)

RETURN MONEY

AS

BEGIN

    RETURN @Price – AveragePrice()

END

使用UDF可以大大增加查询语句的可读性,并实现了代码重用:

USE pubs

SELECT Title,

    Price,

    AveragePrice() AS Average,

    PriceDifference(Price) AS Difference

FROM Titles

WHERE Type = 'popular_comp'

13.3 返回表的UDF

可以对UDF返回的表执行JOIN,甚至对结果应用WHERE条件。相对简单的函数示例如下:

USE pubs

GO

 

CREATE FUNCTION fnAuthorList()

RETURN TABLE

AS

RETURN (

SELECT au_id,

    au_lname + ', ' + au_fname AS au_name

    address AS address1,

    city + ', ' + state + ', ' + zip AS address2

FROM authors

)

GO

这样的话,使用这个函数就像使用表一样:

SELECT *

FROM fnAuthorList()

使用返回表的UDF比使用视图的好处在于可以在UDF中将条件参数化,而视图不得不包含不想要的数据,然后再通过WHERE子句过滤。例如:

CREATE VIEW vSalesCount

AS

SELECT au.au_id,

    au.aulname + ', ' + au.au_fname AS au_name,

    au.address AS address1,

    city + ', ' + state + ', ' + zip AS address2,

    SUM(s.qty) AS SalesCount

FROM authors au

INNER JOIN titleauthor ta

    ON au.au_id = ta.au_id

INNER JOIN sales s

    ON ta.title_id = s.title_id

GROUP BY au.au_id,

    au.au_lname + ', ' + au.au_fname,

    au.address,

    au.city + ', ' + au.state + ', ' + zip

GO

 

SELECT au_name, address1, address2

FROM vSalesCount

WHERE SalesCount > 25

为了使得代码更简单,把所有的代码都封装到一个函数中:

USE pubs

GO

 

CREATE FUNCTION fnSalesCount(@SalesQty BIGINT)

RETURNS TABLE

AS

RETURN (

SELECT au.au_id,

    au.aulname + ', ' + au.au_fname AS au_name,

    au.address AS address1,

    city + ', ' + state + ', ' + zip AS address2,

    SUM(s.qty) AS SalesCount

FROM authors au

INNER JOIN titleauthor ta

    ON au.au_id = ta.au_id

INNER JOIN sales s

    ON ta.title_id = s.title_id

GROUP BY au.au_id,

    au.au_lname + ', ' + au.au_fname,

    au.address,

    au.city + ', ' + au.state + ', ' + zip

HAVING SUM(qty) > @SalesQty

)

GO

为了执行该函数,只需要调用它并提供参数:

SELECT *

FROM fnSalesCount(25)

再进一步,如果需要查询每一个销售超过25本书以上的作者和出版社的信息,这需要连接UDF返回的表:

SELECT DISTINCT p.pub_name, a.au_name

FROM dbo.fnSalesCount(25) AS a

INNER JOIN titleauthor AS ta

    ON a.au_id = ta.au_id

INNER JOIN titles AS t

    ON ta.title_id = t.title_id

INNER JOIN publishers AS p

    ON t.pub_id = p.pub_id

这里对函数进行了连接,就好像它是表或视图一样。唯一的区别在于可以对它进行参数化。

再进一步,UDF也可以递归调用,并同样存在最深32层的限制。例如,现在需要查询向Andrew Fuller报告的报告链的所有人员:

CREATE FUNCTION dbo.fnGetReports(@EmployeeID AS INT)

RETURNS @Reports TABLE (

    EmployeeID INT NOT NULL,

    ReportsToID INT NULL

)

AS

BEGIN

    DECLARE @Employee AS INT

    

    INERT INTO @Reports

    SELECT EmployeeID, ReportsTo

    FROM Employees

    WHERE EmployeeID = @EmployeeID

    

    SELECT @Employee = MIN(EmployeeID)

    FROM Employees

    WHERE ReportsTo = @EmployeeID

    

    WHILE @Employee IS NOT NULL

    BEGIN

        INSERT INTO @Reports

        SELECT *

        FROM fnGetReports(@Employee)

        

        SELECT @Employee = MIN(EmployeeID)

        FROM Employees

        WHERE EmployeeID > @Employee AND ReportsTo = @EmployeeID

    END

    

    RETURN

END

GO

现在如果需要查询员工ID为2的员工的报告链:

SELECT * FROM fnGetReports(2)

理解决定性

如果SQL Server需要建立一个索引的话,则它必须能决定性地定义(明确定义)对哪个项目进行索引。用户自定义函数可以是决定性的也可以是非决定性的。决定性并不是根据任何参数类型来定义的,而是根据函数的功能来定义的。如果给定了一个特有的有效输入集的话,那么每次函数就都能返回相同的结果,我们就说该函数是决定性的。内建函数是决定性的一个示例是SUM()。3、5、10的总和永远是18。但是GETDATE()的值就是非决定性的,因为每次调用它的时候它都会改变。

为了达到决定性的要求,函数必须满足4个标准:

  • 函数必须是限于模式的。这意味着函数所依赖的任何对象会有一个依赖记录,并且在没有删除这个依赖的函数之前都不允许改变这些对象。
  • 函数引用到的所有其他函数,都必须是决定性的。
  • 不能引用定义在函数本身之外的表(可以使用表变量和临时表)。
  • 不能在函数内部使用扩展的存储过程。

决定性的重要性在于它揭示了是否要在视图或计算列上建立索引。这意味着,如果视图或计算列引用非决定性函数的话,则在该视图或列上将不允许建立任何索引。

可以利用OBJECTPROPERTY函数检查一个函数是不是决定性的——它检验存储在对象的IsDeterministic属性来得到结论。例如,我们可以检验DayOnly函数的决定性:

SELECT OBJECTPROPERTY(OBJECT_ID('DayOnly'), 'IsDeterministic')

给DayOnly()函数添加上WITH SCHEMABINDING选项后,该函数将是决定性的:

ALTER FUNCTION DayOnly(@Date DATETIME)

RETURNS VARCHAR(12)

WITH SCHEMABINDING

AS

BEGIN

    RETURN CONVERT(VARCHAR(12), @Date, 101)

END

原文地址:https://www.cnblogs.com/qwertWZ/p/3052209.html