UDF的定义
定义:和存储过程很类似,用户自定义函数是一组有序的T-SQL语句,这些语句被预先优化和编译,并且昨晚一个单元进行调用。Upf和存储过程的主要区别在于结果的返回方式,为了能支持多种不同的返回值,UDF比存储过程有更多的限制。使用存储过程时可传入参数,也可以传出参数。可以返回值,不过该值用于指示成功或失败,而非返回数据。也可以返回结果集,但是在没有将结果集插入到某种表(通常是临时表)中以供后面的情况下,不能查询中真正使用它们。然而,使用UDF时可传入参数,但不可传出参数。输入参数的概念被更为健壮的返回值取代了。和系统函数一样,可以返回标量值,这个值的好处是它并不像在存储过程中那样只限于整型数据,而是可以返回大多数的SQLServer
数据类型。
UDF返回值有以下两种类型。
●返回标量值的UDF
●返回表的UDF
返回标量值的UDF
UDF的真正有点在于返回值并不限于整数,而是可以返回除了BLOB、游标(cursor)和时间戳(timestamp)以外的任何有效的SQL Server数据类型(包括用户自定义类型)。即使想返回整数,UDF也有一下两个吸引人的方面:
●与存储过程不同,用户自定义函数返回值的目的是提供有意义的数据;而对于存储过程来说,返回值只是说明成功和失败,如果失败,则会提供一些关于失败性质的特定信息。
●可在查询中内联执行函数(例如,作为SELECT语句的一部分包含它),而使用存储过程则不行。
案例:
--select * from Orders where OrderDate between DATEADD(mm,3,SYSDATETIME()) and DATEADD(mm,15,SYSDATETIME())
--declare @YearEnding date = DATEADD(MONTH,15,SYSDATETIME());
--select * from Orders where OrderDate between DATEADD(day,1,DATEADD(year,-1,@YearEnding)) and @YearEnding;
--declare @YearEnding date = DATEADD(MONTH,15,SYSDATETIME());
--select * from Orders where OrderDate between DATEADD(day,1,DATEADD(year,-1,@YearEnding)) and @YearEnding;
创建标量值的UDF
declare @YearEnding DATE = DATEADD(MONTH,15,SYSDATETIME());
测试标量值的UDF
select *from Orders where dbo.IsInFiscalYear(@YearEnding,OrderDate)=1;
select *from Orders where dbo.IsInFiscalYear(@YearEnding,OrderDate)=1;
注意:标量值函数必须采用架构
嵌套的内置函数
---UDF嵌套使用
/*select
Name,
ListPrice,
(select AVG(ListPrice) from Production.Product) as Average,
ListPrice-(select AVG(ListPrice) from Production.Product) as Differences
from Production.Product
where ProductSubcategoryID = 1;*/
/*--创建平均值udf
create function dbo.AvarigeUdf()
returns money
with schemabinding--一旦视图被指定了WITH SCHEMABINDING 选项,那么,在修改用于生成当前视图的表或视图时,一旦对当前视图产生影响(导致视图失效),则不允许修改。
as
begin
return (select AVG(ListPrice) from Production.Product);
end
/*select
Name,
ListPrice,
(select AVG(ListPrice) from Production.Product) as Average,
ListPrice-(select AVG(ListPrice) from Production.Product) as Differences
from Production.Product
where ProductSubcategoryID = 1;*/
/*--创建平均值udf
create function dbo.AvarigeUdf()
returns money
with schemabinding--一旦视图被指定了WITH SCHEMABINDING 选项,那么,在修改用于生成当前视图的表或视图时,一旦对当前视图产生影响(导致视图失效),则不允许修改。
as
begin
return (select AVG(ListPrice) from Production.Product);
end
--创建价格减去平均值
GO
create function dbo.DifferencesUdf(@ListPrice money)
returns money
as
begin
return @ListPrice- dbo.AvarigeUdf();
end
GO*/
select Name,ListPrice,dbo.AvarigeUdf() as Avarige,dbo.DifferencesUdf(ListPrice) as Differences from Production.Product where ProductSubcategoryID = 1;
返回表的UDF
内联UDF
案例1:创建简单的返回表的UDF
--创建返回表的UDF
create function dbo.fnContactList()
returns table
as
--不能用begin/end
return (select BusinessEntityID,LastName+','+FirstName as Name from Person.Person);
go
--测试返回内容
select *from dbo.fnContactList();
create function dbo.fnContactList()
returns table
as
--不能用begin/end
return (select BusinessEntityID,LastName+','+FirstName as Name from Person.Person);
go
--测试返回内容
select *from dbo.fnContactList();
案例2:给返回表添加where条件
--创建视图
create view vFullContactName
as
select p.BusinessEntityID,LastName+','+FirstName as Name,ea.EmailAddress from Person.Person as p left join Person.EmailAddress ea on ea.BusinessEntityID = p.BusinessEntityID;
go
--视图不能参数化
select * from vFullContactName where Name LIKE 'Ad%';
create view vFullContactName
as
select p.BusinessEntityID,LastName+','+FirstName as Name,ea.EmailAddress from Person.Person as p left join Person.EmailAddress ea on ea.BusinessEntityID = p.BusinessEntityID;
go
--视图不能参数化
select * from vFullContactName where Name LIKE 'Ad%';
注意:视图不能参数化,视图比较适合静态的
--创建内置函数
create function udfFullContactName(@StrWhere varchar(50))
returns table
as
return (select p.BusinessEntityID,
LastName+','+FirstName as Name,
ea.EmailAddress
from Person.Person as p
left join Person.EmailAddress ea on
ea.BusinessEntityID = p.BusinessEntityID
where LastName like @StrWhere+'%');
go
--测试函数
select * from udfFullContactName('Ad');
create function udfFullContactName(@StrWhere varchar(50))
returns table
as
return (select p.BusinessEntityID,
LastName+','+FirstName as Name,
ea.EmailAddress
from Person.Person as p
left join Person.EmailAddress ea on
ea.BusinessEntityID = p.BusinessEntityID
where LastName like @StrWhere+'%');
go
--测试函数
select * from udfFullContactName('Ad');
注意:虽然存储过程也可以实现但是不能把存储过程的结果和其他表连起来。
内置函数可以返回使用多条创建的表——唯一的区别在于必须对需要返回的元数据(就像临时表一样)进行命名和定义。
案例:递归调用
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
create function dbo.fnGetReports
(@EmployeeID AS int)
returns @Reports table(
EmployeeID int not null,
ManagerID int null
)
as
begin
declare @Employee as int;
insert into @Reports
select EmployeeID,ManagerID
from HumanResources.Employee2
where EmployeeID =@EmployeeID;
select @Employee = MIN(@EmployeeID)
from HumanResources.Employee2 where ManagerID = @EmployeeID;
while @Employee is not null
begin
insert into @Reports
select * from fnGetReports(@Employee);
select @Employee = MIN(EmployeeID)
from HumanResources.Employee2
where EmployeeID > @Employee
and ManagerID = @EmployeeID;
end
return;
end
go
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
测试
(@EmployeeID AS int)
returns @Reports table(
EmployeeID int not null,
ManagerID int null
)
as
begin
declare @Employee as int;
insert into @Reports
select EmployeeID,ManagerID
from HumanResources.Employee2
where EmployeeID =@EmployeeID;
select @Employee = MIN(@EmployeeID)
from HumanResources.Employee2 where ManagerID = @EmployeeID;
while @Employee is not null
begin
insert into @Reports
select * from fnGetReports(@Employee);
select @Employee = MIN(EmployeeID)
from HumanResources.Employee2
where EmployeeID > @Employee
and ManagerID = @EmployeeID;
end
return;
end
go
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
测试
select * from fnGetReports(3);
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
declare @EmployeeID int;
select
@EmployeeID = EmployeeID
from HumanResources.Employee2 e where LastName = 'Huntington'
and FirstName = 'Karla';
select
e.EmployeeID,
e.LastName,
e.FirstName,
m.LastName as ReportTo
from HumanResources.Employee2 as e
join dbo.fnGetReports(@EmployeeID) as r
on e.EmployeeID = r.EmployeeID
join HumanResources.Employee2 as m
on m.EmployeeID = r.ManagerID;
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
declare @EmployeeID int;
select
@EmployeeID = EmployeeID
from HumanResources.Employee2 e where LastName = 'Huntington'
and FirstName = 'Karla';
select
e.EmployeeID,
e.LastName,
e.FirstName,
m.LastName as ReportTo
from HumanResources.Employee2 as e
join dbo.fnGetReports(@EmployeeID) as r
on e.EmployeeID = r.EmployeeID
join HumanResources.Employee2 as m
on m.EmployeeID = r.ManagerID;
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
理解确定性
目的:有一些函数需要为将创建索引的对象(例如计算列或索引视图)提供数据。
定义:用户自定义函数可以是确定的,也可以是非确定性的。确定性并不是根据任何参数类型定义的,而是根据SQL Server 表名的函数的功能定义的,如果给定了医嘱特定的有效输入,每次函数都能返回相同的结果,就是该函数是有确定性的,SUN()就是一个确定性的内置函数。但是GETDATE()的值是非确定性的,因为每次调用它的时候GETDATE()都会改变。
为了达到 确定性的要求,函数必须满足一下4个条件。
● 函数必须是架构绑定的。这意味着函数所依赖的任何对象会有一个依赖记录,并且在没有删除这个依赖函数之前都不允许改变这些对象。
● 函数应用的所有其他函数,无论其是用户定义的还是系统定义的,都必须是确定性的。
● 不能引用在函数外部定义的表(可以使用表变量和临时表,只要他们是在函数作用域内定义就行)。
●不能使用扩展存储过程。
确定性的重要性在于它显示了是否在视图或计算机列上建立索引。只有在能够可靠的确定视图或计算列的结构时,才允许在视图或计算咧上建立索引。这意味着,如果视图或计算机引用非确定性函数,则在该视图或裂伤将不允许建立任何索引。
判断函数是否是确定性:
select OBJECTPROPERTY(OBJECT_ID('函数名-不包含括号和参数'),'IsDeterministic');
结果中,0为假非0为真
确定函数案例:
alter function IsInFiscalYear(@YearEnding DATE,@CompareDate DATE)
returns bit
with schemabinding
as
begin
declare @out bit =0;
if @CompareDate between
DATEADD(day,1,DATEADD(YEAR,-1,@YearEnding)) and @YearEnding
set @out =1
return @out;
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
create function dbo.salesOrderDate(@SalesOrderID int)
returns date
with schemabinding
as
begin
declare @SODate DATE;
SELECT @SODate = OrderDate from Sales.SalesOrderHeader
where SalesOrderID = @SalesOrderID;
return @SoDate;
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--select OBJECTPROPERTY(OBJECT_ID('salesOrderDate'),'IsDeterministic');--检验是否是确定性函数
returns bit
with schemabinding
as
begin
declare @out bit =0;
if @CompareDate between
DATEADD(day,1,DATEADD(YEAR,-1,@YearEnding)) and @YearEnding
set @out =1
return @out;
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
create function dbo.salesOrderDate(@SalesOrderID int)
returns date
with schemabinding
as
begin
declare @SODate DATE;
SELECT @SODate = OrderDate from Sales.SalesOrderHeader
where SalesOrderID = @SalesOrderID;
return @SoDate;
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--select OBJECTPROPERTY(OBJECT_ID('salesOrderDate'),'IsDeterministic');--检验是否是确定性函数
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
create function dbo.salesOrderAge(@SalesOrderID int,@CurrentDate date)
returns int
with schemabinding
as
begin
declare @SODate Date,@SOAge INT;
set @SODate = dbo.salesOrderDate(@SalesOrderID);
set @SOAge = DATEDIFF(DAY,@SODate,@CurrentDate);
return @SOAge;
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
select objectproperty(object_id('salesOrderAge'),'IsDeterministic');
create function dbo.salesOrderAge(@SalesOrderID int,@CurrentDate date)
returns int
with schemabinding
as
begin
declare @SODate Date,@SOAge INT;
set @SODate = dbo.salesOrderDate(@SalesOrderID);
set @SOAge = DATEDIFF(DAY,@SODate,@CurrentDate);
return @SOAge;
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
select objectproperty(object_id('salesOrderAge'),'IsDeterministic');