函数与存储过程
比方说,我必须实现一个必须返回一个表作为结果的T-SQL代码。 我可以实现一个表值函数或者一个返回一组行的存储过程。 我应该使用什么?
总之,我想知道的是:
函数和存储过程之间的主要区别是哪些? 我需要考虑使用哪一种考虑因素?
如果你可能想把这段代码的结果与其他表结合起来,那么显然一个表值函数将允许你在一个SELECT语句中编写结果。
一般来说,有一个层次结构(View <TV Function <Stored Proc)。 你可以在每一个中做更多的事情,但是组合输出的能力和优化器真正参与的能力随着function的增加而减less。
因此,使用最低限度允许你expression你想要的结果。
函数必须是确定性的,不能用来修改数据库,而存储过程允许你插入和更新等。
您应该限制您对函数的使用,因为这会对大型复杂的查询造成巨大的可扩展性问题。 他们成为查询优化器的“黑匣子”,你会发现在使用函数和简单地将代码插入查询之间会出现巨大的性能差异。
但是在非常特殊的情况下,它们对于表值返回肯定是有用的。
如果您需要parsing逗号分隔的列表,为了模拟将数组传递给过程,函数可以将列表转换为表格。 这是Sql Server 2005的常见做法,因为我们无法将表传递给存储过程(我们可以在2008年)。
从文档 :
如果一个存储过程符合以下标准,那么它是一个重写为表值函数的好select:
逻辑可以用单个SELECT语句来表示,但仅仅是因为需要参数而是存储过程而不是视图。
存储过程不执行更新操作,除了表variables。
不需要dynamic的EXECUTE语句。
存储过程返回一个结果集。
存储过程的主要目的是构build要加载到临时表中的中间结果,然后在SELECT语句中查询该临时表。
我将在存储过程和函数之间写出一些有趣的差异。
- 我们可以在select查询中使用函数,但是我们不能在select查询中使用存储过程。
-
我们不能在函数中使用非确定性函数,但是我们可以在存储过程中使用非确定性函数。 现在问题出现了,什么是非确定性函数?Ans是: –
非确定性函数是在不同时间为相同input值返回不同输出的函数,如getdate()。 每当运行时它总是返回不同的值。
例外:-
在sql 2000之前的早期版本的sql server不允许在用户定义的函数中使用getdate()函数,但2005版本和之后的版本允许我们在用户定义的函数中使用getdate()函数。
Newid()是非确定性函数的另一个例子,但不能在用户定义函数中使用,但是我们可以在存储过程中使用它。
-
我们可以在存储过程中使用DML(插入,更新,删除)语句,但是我们不能在物理表或永久表上的函数中使用DML语句。 如果我们想在函数中执行DML操作,那么我们可以通过表variables而不是永久表来实现。
-
我们不能在函数中使用error handling,但是我们可以在存储过程中进行error handling。
-
过程可以返回零或n值,而函数可以返回一个必需的值。
-
程序可以有input/输出参数,而函数只能有input参数。
-
程序允许select以及DML语句,而函数只允许select语句。
-
函数可以从程序调用,而程序不能从函数调用。
-
exception可以通过try-catch块来处理,而try-catch块不能在函数中使用。
-
我们可以在程序中进行交易pipe理,而不能在function上进行。
-
程序不能在select语句中使用,而函数可以embedded到select语句中。
-
UDF(用户定义函数)可以用在
WHERE
/HAVING
/SELECT
部分任何地方的SQL语句中,而存储过程不能。 -
返回表的UDF可以被视为另一个行集。 这可以在
JOIN
与其他表一起使用。 -
内联UDF可以作为视图来获取参数,并可以在
JOIN
和其他行集操作中使用。
例如,如果你有一个函数可以用它作为你的SQL语句的一部分
SELECT function_name(field1) FROM table
这不适用于存储过程。
我运行了一些testing,运行一长串逻辑,在表值函数和存储过程中运行相同的代码(一个长的SELECT语句),以及一个直接的EXEC / SELECT,并且每个都执行相同的操作。
在我看来,总是使用表值函数而不是存储过程来返回一个结果集,因为它使查询中的逻辑更加容易和可读,随后join到它们中,并使您可以重用相同的逻辑。 为了避免太多的性能影响,我经常使用“可选”参数(即可以将NULL传递给它们)来使函数返回结果集更快,例如:
CREATE FUNCTION dbo.getSitePermissions(@RegionID int, @optPersonID int, optSiteID int) AS RETURN SELECT DISTINCT SiteID, PersonID FROM dbo.SiteViewPermissions WHERE (@optPersonID IS NULL OR @optPersonID = PersonID) AND (@optSiteID IS NULL OR @optSiteID = SiteID) AND @RegionID = RegionID
这样你就可以在很多不同的情况下使用这个函数,并且不会造成巨大的性能下降。 我相信这比之后的过滤更有效率:
SELECT * FROM dbo.getSitePermissions(@RegionID) WHERE SiteID = 1
我已经在几个函数中使用了这种技术,有时候还有一个这种types的“可选”参数的长列表。
我个人使用表值函数,当我所有返回的是一个没有影响的表。 基本上我把它们当作参数化视图。
如果我需要多个logging集返回,或者如果表中有值更新,我使用存储过程。
我的2美分
如上所述,函数更具可读性/可组合性/自我logging,但通常性能较差,而且如果在诸如
SELECT * FROM dbo.tvfVeryLargeResultset1(@myVar1) tvf1 INNER JOIN dbo.tvfVeryLargeResultset1(@myVar2) tvf2 ON (tvf1.JoinId = tvf2.JoinId)
通常情况下,您只需接受一个tvf可以消除的冗余代码(以不可接受的性能成本)。
我还没有提到的另一点是,你不能在多语句的tvf中使用数据库状态改变的临时表。 对于临时表而言,在function上最为等价的机制是内存表variables中的非状态更改,对于大型数据集,临时表可能会比表variables更具性能。 (其他select还包括dynamic表格和常用表格expression式,但在某种程度上的复杂性,这些不再是海事组织的一个好select。)
我会执行testing。 sp方法或派生表很可能比函数快得多,如果这样的话应该使用这种方法。 一般来说,我会避免函数,因为它们可能会成为性能猪。
这取决于:)如果你想在另一个过程中使用表值结果,最好使用TableValued函数。 如果结果是针对客户端的,则存储过程通常是更好的方法。
对于存储过程和函数,请访问:
MS SQL Server中的存储过程和函数
存储过程在MS Sql Server 2008中