创建用户定义函数。这是一个已保存 Transact-SQL 或公共语言运行时 (CLR) 例程,该例程可返回一个值。用户定义函数不能用于执行修改数据库状态的操作。与系统函数一样,用户定义函数可从查询中调用。标量函数和存储过程一样,可使用 EXECUTE 语句执行。
用户定义函数可使用 ALTER FUNCTION 修改,使用 DROP FUNCTION 删除。
Transact-SQL 语法约定
Scalar Functions CREATE FUNCTION [ schema_name. ] function_name ( [ { @parameter_name [ AS ][ type_schema_name. ] parameter_data_type [ = default ] } [ ,...n ] ] ) RETURNS return_data_type [ WITH |
用户定义函数为标量值函数或表值函数。如果 RETURNS 子句指定了一种标量数据类型,则函数为标量值函数。可以使用多条 Transact-SQL 语句定义标量值函数。
如果 RETURNS 子句指定 TABLE,则函数为表值函数。根据函数主体的定义方式,表值函数可分为内联函数或多语句函数。有关详细信息,请参阅表值用户定义函数。
下列语句在函数内有效:
用户定义函数可以嵌套;也就是说,用户定义函数可相互调用。被调用函数开始执行时,嵌套级别将增加;被调用函数执行结束后,嵌套级别将减少。用户定义函数的嵌套级别最多可达 32 级。如果超出最大嵌套级别数,整个调用函数链将失败。
注意: |
---|
从 Transact-SQL 用户定义函数对托管代码的任何引用都将计入 32 级嵌套限制的一个级别。从托管代码内部调用的方法不根据此限制进行计数。 |
在 SQL Server 的早期版本中,函数只能分为确定性函数和不确定性函数两类。在 SQL Server 2005 中,函数具有下列属性。这些属性的值确定了函数是否可用于持久化计算列或索引计算列。
属性 | 说明 | 注意 |
---|---|---|
IsDeterministic |
函数是确定性函数还是不确定性函数。 |
确定性函数中允许本地数据访问。例如,如果每次使用一组特定输入值和相同数据库状态调用函数时,函数都返回相同结果,则该函数将被标记为确定性函数。 |
IsPrecise |
函数是精确函数还是不精确函数。 |
不精确函数包含浮点运算之类的运算。 |
IsSystemVerified |
SQL Server 可验证函数的精度和确定性属性。 |
|
SystemDataAccess |
函数可以访问 SQL Server 的本地实例中的系统数据(系统目录或虚拟系统表)。 |
|
UserDataAccess |
函数可以访问 SQL Server 的本地实例中的用户数据。 |
包含用户定义表和临时表,但不包含表变量。 |
Transact-SQL 函数的精度和确定性属性由 SQL Server 自动确定。有关详细信息,请参阅用户定义函数的设计指导原则。CLR 函数的数据访问权限和确定性属性可由用户指定。有关详细信息,请参阅 Overview of CLR Integration Custom Attributes。
若要显示这些属性的当前值,请使用 OBJECTPROPERTYEX。
如果用户定义函数具有下列属性值,则可以在索引中使用调用用户定义函数的计算列:
有关详细信息,请参阅为计算列创建索引。
如果在函数中调用扩展存储过程,则该过程不能向客户端返回结果集。向客户端返回结果集的任何 ODS API 都将返回 FAIL。扩展存储过程可以连接回 SQL Server 的实例;不过,该过程不应尝试与调用扩展存储过程的函数同时联接到同一事务。
与通过批处理或存储过程进行调用相似,扩展存储过程在运行 SQL Server 的 Windows 安全帐户的上下文中执行。存储过程的所有者在授予用户 EXECUTE 权限时应考虑这一点。
可在使用标量表达式的位置调用标量值函数。这包括计算列和 CHECK 约束定义。也可以使用 EXECUTE 语句执行标量值函数。在允许表表达式的情况下,可在 SELECT、INSERT、UPDATE 或 DELETE 语句的 FROM 子句中调用表值函数。有关详细信息,请参阅执行用户定义函数(数据库引擎)。
如果在 CLR 函数中指定了参数,则这些参数应为 SQL Server 类型,即以前为 scalar_parameter_data_type 定义的类型。有关将 SQL Server 系统数据类型与 CLR 集成数据类型或 .NET Framework 公共语言运行时数据类型进行比较的信息,请参阅 SQL Server Data Types and Their .NET Framework Equivalents。
为了使 SQL Server 在类中重载时引用正确方法,
如果 CLR 函数的返回数据类型指定表类型 (RETURNS TABLE),则
有关对 CLR 函数编程的详细信息,请参阅 CLR User-Defined Functions。
下列 Service Broker 语句不能包含在 Transact-SQL 用户定义函数的定义中:
若要显示 Transact-SQL 用户定义函数的定义,请使用函数所在数据库中的 sys.sql_modules 目录视图。
例如:
复制代码 | |
---|---|
USE AdventureWorks; GO SELECT Definition FROM sys.sql_modules AS m JOIN sys.objects AS o ON m.object_id = o.object_id AND TYPE IN ('FN', 'IF', 'TF'); GO |
注意: |
---|
不能使用 sys.sql_modules 查看使用 ENCRYPTION 选项创建的函数定义;不过,可显示有关加密函数的其他信息。 |
若要显示有关 CLR 用户定义函数的信息,请使用函数所在数据库中的 sys.assembly_modules 目录视图。
若要显示有关用户定义函数中定义的参数的信息,请使用函数所在数据库中的 sys.parameters 目录视图。
若要显示有关函数引用的对象的报表,请使用 sys.sql_dependencies。
用户定义函数所属的架构的名称。
用户定义函数的名称。函数名称必须符合有关标识符的规则,并且在数据库中以及对其架构来说是唯一的。
注意: |
---|
即使未指定参数,函数名称后也需要加上括号。 |
用户定义函数的参数。可声明一个或多个参数。
函数最多可以有 1,024 个参数。执行函数时,如果未定义参数的默认值,则用户必须提供每个已声明参数的值。
通过将 at 符号 (@) 用作第一个字符来指定参数名称。参数名称必须符合有关标识符的规则。参数是对应于函数的局部参数;其他函数中可使用相同的参数名称。参数只能代替常量,而不能用于代替表名、列名或其他数据库对象的名称。
注意: |
---|
在存储过程或用户定义函数中传递参数时,或在批语句中声明和设置变量时,不会遵守 ANSI_WARNINGS。例如,如果将变量定义为 char(3) 类型,然后将其值设置为多于三个字符,则数据将截断为定义大小,并且 INSERT 或 UPDATE 语句可以成功执行。 |
参数的数据类型及其所属的架构,后者为可选项。对于 Transact-SQL 函数,可以使用除 timestamp 数据类型之外的所有数据类型(包括 CLR 用户定义类型)。对于 CLR 函数,可以使用除 text、ntext、image 和 timestamp 数据类型之外的所有数据类型(包括 CLR 用户定义类型)。不能将非标量类型 cursor 和 table 指定为 Transact-SQL 函数或 CLR 函数中的参数数据类型。
如果未指定 type_schema_name,则 SQL Server 2005 Database Engine 将按以下顺序查找 scalar_parameter_data_type:
参数的默认值。如果定义了 default 值,则无需指定此参数的值即可执行函数。
注意: |
---|
可以为除 varchar(max) 和 varbinary(max) 数据类型之外的 CLR 函数指定默认参数值。 |
如果函数的参数有默认值,则该函数检索默认值时必须指定 DEFAULT 关键字。此行为与在存储过程中使用具有默认值的参数不同,在后一种情况下,不提供参数同样意味着使用默认值。
标量用户定义函数的返回值。对于 Transact-SQL 函数,可以使用除 timestamp 数据类型之外的所有数据类型(包括 CLR 用户定义类型)。对于 CLR 函数,可以使用除 text、ntext、image 和 timestamp 数据类型之外的所有数据类型(包括 CLR 用户定义类型)。不能将非标量类型 cursor 和 table 指定为 Transact-SQL 函数或 CLR 函数中的返回数据类型。
指定一系列定义函数值的 Transact-SQL 语句,这些语句在一起使用不会产生负面影响(例如修改表)。function_body 仅用于标量函数和多语句表值函数。
在标量函数中,function_body 是一系列 Transact-SQL 语句,这些语句一起使用的计算结果为标量值。
在多语句表值函数中,function_body 是一系列 Transact-SQL 语句,这些语句将填充 TABLE 返回变量。
指定标量函数返回的标量值。
指定表值函数的返回值为表。只有常量和 @local_variables 可以传递到表值函数。
在内联表值函数中,TABLE 返回值是通过单个 SELECT 语句定义的。内联函数没有关联的返回变量。
在多语句表值函数中,@return_variable 是 TABLE 变量,用于存储和汇总应作为函数值返回的行。只能将 @return_variable 指定用于 Transact-SQL 函数,而不能用于 CLR 函数。
定义内联表值函数的返回值的单个 SELECT 语句。
指定将程序集与函数绑定的方法。assembly_name 必须与 SQL Server 中当前数据库内具有可见性的现有程序集匹配。class_name 必须是有效的 SQL Server 标识符,并且必须作为类存在于程序集中。如果类具有以命名空间限定的名称,该名称使用句点 (.) 来分隔命名空间的各部分,则必须使用方括号 ([ ]) 或引号 (" ") 分隔类名称。method_name 必须是有效的 SQL Server 标识符,并且必须作为静态方法存在于指定类中。
注意: |
---|
默认情况下,SQL Server 不能执行 CLR 代码。可以创建、修改和删除引用公共语言运行时模块的数据库对象;不过,只有在启用 clr enabled 选项之后,才能在 SQL Server 中执行这些引用。若要启用此选项,请使用 sp_configure。 |
定义 Transact-SQL 函数的表数据类型。表声明包含列定义和列约束(或表约束)。表始终放在主文件组中。
定义 CLR 函数的表数据类型。表声明仅包含列名称和数据类型。表始终放在主文件组中。
指定函数将具有以下一个或多个选项:
指示数据库引擎 对包含 CREATE FUNCTION 语句文本的目录视图列进行加密。使用 ENCRYPTION 可以防止将函数作为 SQL Server 复制的一部分发布。不能为 CLR 函数指定 ENCRYPTION。
指定将函数绑定到其引用的数据库对象。如果其他架构绑定对象也在引用该函数,此条件将防止对其进行更改。
只有发生下列操作之一时,才会删除函数与其引用对象的绑定:
只有满足以下条件时,函数才能绑定到架构:
不能为 CLR 函数或引用别名数据类型的函数指定 SCHEMABINDING。
指定标量值函数的 OnNULLCall 属性。如果未指定,则默认为 CALLED ON NULL INPUT。这意味着即使传递的参数为 NULL,也将执行函数体。
如果在 CLR 函数中指定了 RETURNS NULL ON NULL INPUT,它指示当 SQL Server 接收到的任何一个参数为 NULL 时,它可以返回 NULL,而无需实际调用函数体。如果
指定用于执行用户定义函数的安全上下文。所以,您可以控制 SQL Server 使用哪一个用户帐户来验证针对该函数引用的任何数据库对象的权限。
注意: |
---|
不能为内联用户定义函数指定 EXECUTE AS。 |
有关详细信息,请参阅EXECUTE AS 子句 (Transact-SQL)。
< column_definition >::=
定义表数据类型。表声明包含列定义和约束。对于 CLR 函数,只能指定 column_name 和 data_type。
表中列的名称。列名称必须符合标识符规则,并且在表中必须是唯一的。column_name 可以由 1 至 128 个字符组成。
指定列数据类型。对于 Transact-SQL 函数,可以使用除 timestamp 之外的所有数据类型(包括 CLR 用户定义类型)。对于 CLR 函数,可以使用除 text、ntext、image、char、varchar、varchar(max) 和 timestamp 之外的所有数据类型(包括 CLR 用户定义类型)。在 Transact-SQL 或 CLR 函数中,非标量类型 cursor 不能指定为列数据类型。
指定当插入过程中没有显式提供值时为列提供的值。constant_expression 可以是常量、NULL 或系统函数值。DEFAULT 定义可以应用于除具有 IDENTITY 属性的列之外的任何列。不能为 CLR 表值函数指定 DEFAULT。
指定列的排序规则。如果未指定,则为此列分配数据库的默认排序规则。排序规则名称既可以是 Windows 排序规则名称,也可以是 SQL 排序规则名称。有关排序规则的列表及详细信息,请参阅 Windows 排序规则名称 (Transact-SQL)和 SQL 排序规则名称 (Transact-SQL)。
COLLATE 子句只能用来更改数据类型为 char、varchar、nchar 和 nvarchar 的列的排序规则。
不能为 CLR 表值函数指定 COLLATE。
指示新列是行的全局唯一标识符列。对于每个表,只能将其中的一个 uniqueidentifier 列指定为 ROWGUIDCOL 列。ROWGUIDCOL 属性只能分配给 uniqueidentifier 列。
ROWGUIDCOL 属性并不强制实现列中存储的值的唯一性。该属性也不会为插入表的新行自动生成值。若要为每列生成唯一值,请在 INSERT 语句中使用 NEWID 函数。可以指定默认值;但是,不能将 NEWID 指定为默认值。
指示新列是标识列。在为表添加新行时,SQL Server 将为该列提供唯一的增量值。标识列通常与 PRIMARY KEY 约束一起使用,作为表的唯一行标识符。可以将 IDENTITY 属性分配给 tinyint、smallint、int、bigint、decimal(p,0) 或 numeric(p,0) 列。每个表只能创建一个标识列。不能将绑定默认值和 DEFAULT 约束用于标识列。必须同时指定 seed 和 increment,或者二者都不指定。如果二者都未指定,则取默认值 (1,1)。
不能为 CLR 表值函数指定 IDENTITY。
要分配给表中第一行的整数值。
要加到表中后续行的 seed 值上的整数值。
< column_constraint >::= and < table_constraint>::=
为指定列或表定义约束。对于 CLR 函数,允许的唯一约束类型为 NULL。不允许命名约束。
确定列中是否允许空值。严格讲来,NULL 不是约束,但可以像指定 NOT NULL 那样指定它。不能为 CLR 表值函数指定 NOT NULL。
一个约束,该约束通过唯一索引来强制指定列的实体完整性。在表值用户定义函数中,只能对每个表中的一列创建 PRIMARY KEY 约束。不能为 CLR 表值函数指定 PRIMARY KEY。
一个约束,该约束通过唯一索引为一个或多个指定列提供实体完整性。一个表可以有多个 UNIQUE 约束。不能为 CLR 表值函数指定 UNIQUE。
指示为 PRIMARY KEY 或 UNIQUE 约束创建聚集索引还是非聚集索引。PRIMARY KEY 约束使用 CLUSTERED,而 UNIQUE 约束使用 NONCLUSTERED。
只能为一个约束指定 CLUSTERED。如果为 UNIQUE 约束指定了 CLUSTERED,并且指定了 PRIMARY KEY 约束,则 PRIMARY KEY 使用 NONCLUSTERED。
不能为 CLR 表值函数指定 CLUSTERED 和 NONCLUSTERED。
一个约束,该约束通过限制可输入一列或多列中的可能值来强制实现域完整性。不能为 CLR 表值函数指定 CHECK 约束。
返回 TRUE 或 FALSE 的逻辑表达式。
指定计算列。有关计算列的详细信息,请参阅 CREATE TABLE (Transact-SQL)。
计算列的名称。
定义计算列的值的表达式。
为 PRIMARY KEY 或 UNIQUE 索引指定索引选项。有关索引选项的详细信息,请参阅 CREATE INDEX (Transact-SQL)。
指定索引填充。默认值为 OFF。
指定一个百分比,指示在创建或更改索引期间,数据库引擎 对各索引页的叶级填充的程度。fillfactor 必须为介于 1 至 100 之间的整数值。默认值为 0。
指定当对唯一聚集索引或唯一非聚集索引的多行插入事务中出现重复键值时的错误响应。默认值为 OFF。
指定是否重新计算分布统计信息。默认值为 OFF。
指定是否允许行锁。默认值为 ON。
指定是否允许页锁。默认值为 ON。
需要在数据库中具有 CREATE FUNCTION 权限,并对创建函数时所在的架构具有 ALTER 权限。如果函数指定用户定义类型,则需要对该类型具有 EXECUTE 权限。
以下示例将创建用户定义函数 ISOweek
。此函数使用日期参数来计算 ISO 周数。要使此函数能正确计算,必须在调用该函数前调用 SET DATEFIRST 1
。
另外,该示例将显示如何使用 EXECUTE AS
子句指定可在其中执行存储过程的安全上下文。在该示例中,CALLER
选项指定该过程将在调用该过程的用户的上下文中执行。您还可以指定 SELF、OWNER 和 user_name 等其他选项。有关详细信息,请参阅EXECUTE AS 子句 (Transact-SQL)。
复制代码 | |
---|---|
USE AdventureWorks; GO IF OBJECT_ID (N'dbo.ISOweek', N'FN') IS NOT NULL DROP FUNCTION dbo.ISOweek; GO CREATE FUNCTION dbo.ISOweek (@DATE datetime) RETURNS int WITH EXECUTE AS CALLER AS BEGIN DECLARE @ISOweek int SET @ISOweek= DATEPART(wk,@DATE)+1 -DATEPART(wk,CAST(DATEPART(yy,@DATE) as CHAR(4))+'0104') --Special cases: Jan 1-3 may belong to the previous year IF (@ISOweek=0) SET @ISOweek=dbo.ISOweek(CAST(DATEPART(yy,@DATE)-1 AS CHAR(4))+'12'+ CAST(24+DATEPART(DAY,@DATE) AS CHAR(2)))+1 --Special case: Dec 29-31 may belong to the next year IF ((DATEPART(mm,@DATE)=12) AND ((DATEPART(dd,@DATE)-DATEPART(dw,@DATE))>= 28)) SET @ISOweek=1 RETURN(@ISOweek) END; GO |
下面是函数调用。请注意,DATEFIRST
设置为 1
。
复制代码 | |
---|---|
SET DATEFIRST 1; SELECT dbo.ISOweek(CONVERT(DATETIME,'12/26/2004',101)) AS 'ISO Week'; |
下面是结果集:
复制代码 | |
---|---|
ISO Week ---------------- 52 |
以下示例将返回内联表值函数。对于销售给商店的每个产品,该函数返回三列,分别为 ProductID
、Name
以及各个商店年初至今总数的累计 YTD Total
。
复制代码 | |
---|---|
USE AdventureWorks; GO IF OBJECT_ID (N'Sales.fn_SalesByStore', N'IF') IS NOT NULL DROP FUNCTION Sales.fn_SalesByStore; GO CREATE FUNCTION Sales.fn_SalesByStore (@storeid int) RETURNS TABLE AS RETURN ( SELECT P.ProductID, P.Name, SUM(SD.LineTotal) AS 'YTD Total' FROM Production.Product AS P JOIN Sales.SalesOrderDetail AS SD ON SD.ProductID = P.ProductID JOIN Sales.SalesOrderHeader AS SH ON SH.SalesOrderID = SD.SalesOrderID WHERE SH.CustomerID = @storeid GROUP BY P.ProductID, P.Name ); GO |
若要调用该函数,请运行此查询。
复制代码 | |
---|---|
SELECT * FROM Sales.fn_SalesByStore (602); |
以下示例创建了表值函数 fn_FindReports(InEmpID)
。如果提供一个有效雇员 ID,该函数将返回一个表,该表对应于直接或间接向该雇员报告的所有雇员。该函数使用递归公用表表达式 (CTE) 来生成雇员的层次结构列表。有关递归 CTE 的详细信息,请参阅 WITH common_table_expression (Transact-SQL)。
复制代码 | |
---|---|
USE AdventureWorks; GO IF OBJECT_ID (N'dbo.fn_FindReports', N'TF') IS NOT NULL DROP FUNCTION dbo.fn_FindReports; GO CREATE FUNCTION dbo.fn_FindReports (@InEmpID INTEGER) RETURNS @retFindReports TABLE ( EmployeeID int primary key NOT NULL, Name nvarchar(255) NOT NULL, Title nvarchar(50) NOT NULL, EmployeeLevel int NOT NULL, Sort nvarchar (255) NOT NULL ) --Returns a result set that lists all the employees who report to the --specific employee directly or indirectly.*/ AS BEGIN WITH DirectReports(Name, Title, EmployeeID, EmployeeLevel, Sort) AS (SELECT CONVERT(Varchar(255), c.FirstName + ' ' + c.LastName), e.Title, e.EmployeeID, 1, CONVERT(Varchar(255), c.FirstName + ' ' + c.LastName) FROM HumanResources.Employee AS e JOIN Person.Contact AS c ON e.ContactID = c.ContactID WHERE e.EmployeeID = @InEmpID UNION ALL SELECT CONVERT(Varchar(255), REPLICATE ('| ' , EmployeeLevel) + c.FirstName + ' ' + c.LastName), e.Title, e.EmployeeID, EmployeeLevel + 1, CONVERT (Varchar(255), RTRIM(Sort) + '| ' + FirstName + ' ' + LastName) FROM HumanResources.Employee as e JOIN Person.Contact AS c ON e.ContactID = c.ContactID JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID ) -- copy the required columns to the result of the function INSERT @retFindReports SELECT EmployeeID, Name, Title, EmployeeLevel, Sort FROM DirectReports RETURN END; GO -- Example invocation SELECT EmployeeID, Name, Title, EmployeeLevel FROM dbo.fn_FindReports(109) ORDER BY Sort; |
以下示例假定在本地计算机的默认位置安装了 SQL Server Database Engine samples,并且已编译了 StringManipulate.csproj 示例应用程序。有关详细信息,请参阅 能够识别补充字符的字符串操作。
该示例将创建 CLR 函数 len_s
。在创建该函数之前,程序集 SurrogateStringFunction.dll
已在本地数据库中注册。
复制代码 | |
---|---|
DECLARE @SamplesPath nvarchar(1024); -- You may have to modify the value of the this variable if you have --installed the sample someplace other than the default location. SELECT @SamplesPath = REPLACE(physical_name, 'Microsoft SQL Server/MSSQL.1/MSSQL/DATA/master.mdf', 'Microsoft SQL Server/90/Samples/Engine/Programmability/CLR/') FROM master.sys.database_files WHERE name = 'master'; CREATE ASSEMBLY [SurrogateStringFunction] FROM @SamplesPath + 'StringManipulate/CS/StringManipulate/bin/debug/SurrogateStringFunction.dll' WITH PERMISSION_SET = EXTERNAL_ACCESS; GO CREATE FUNCTION [dbo].[len_s] (@str nvarchar(4000)) RETURNS bigint AS EXTERNAL NAME [SurrogateStringFunction].[Microsoft.Samples.SqlServer.SurrogateStringFunction].[LenS]; GO |