如何报告来自SQL Server用户定义函数的错误
我正在SQL Server 2008中编写一个用户定义的函数。我知道函数不能以通常的方式引发错误 – 如果尝试包含RAISERROR语句,则SQL返回:
Msg 443, Level 16, State 14, Procedure ..., Line ... Invalid use of a side-effecting operator 'RAISERROR' within a function.
但事实是,函数需要一些input,这可能是无效的,如果是,则没有有意义的值,函数可以返回。 那我该怎么办?
当然,我可以返回NULL,但使用该函数的任何开发人员将很难对此进行故障排除。 我也可以通过零或类似的东西导致划分 – 这会产生一个错误信息,但是一个误导性的。 有什么办法可以让我自己的错误消息报告吗?
你可以使用CAST来引发有意义的错误:
create function dbo.throwError() returns nvarchar(max) as begin return cast('Error happened here.' as int); end
然后Sql Server会显示一些帮助信息:
Msg 245, Level 16, State 1, Line 1 Conversion failed when converting the varchar value 'Error happened here.' to data type int.
通常的技巧是迫使除数为0.这会引发一个错误,并中断正在评估函数的当前语句。 如果开发人员或支持人员知道这种行为,那么调查和解决问题是相当容易的,因为将0错误划分被理解为不同的,不相关的问题的症状。
从任何angular度来看,这都不好看,但不幸的是,SQL函数的devise目前没有更好的select。 在函数中绝对允许使用RAISERROR。
继弗拉基米尔·科罗廖夫的回答之后,有条件抛出错误的成语是
CREATE FUNCTION [dbo].[Throw] ( @error NVARCHAR(MAX) ) RETURNS BIT AS BEGIN RETURN CAST(@error AS INT) END GO DECLARE @error NVARCHAR(MAX) DECLARE @bit BIT IF `error condition` SET @error = 'My Error' ELSE SET @error = '0' SET @bit = [dbo].[Throw](@error)
UDF中不允许RAISEERROR
或@@ERROR
。 你可以把UDF变成一个有序的程序吗?
来自Erland Sommarskog的文章SQL Server中的error handling – 背景 :
用户定义的函数通常作为SET,SELECT,INSERT,UPDATE或DELETE语句的一部分被调用。 我发现,如果在多语句表值函数或标量函数中出现错误,函数的执行立即中止,函数所属的语句也会立即中止。 执行继续下一行,除非错误中止批处理。 在任何一种情况下,@@ error都是0.因此,无法检测到来自T-SQL的函数中发生错误。
内联表函数不会出现问题,因为内联表值函数基本上是查询处理器粘贴到查询中的macros。
您也可以使用EXEC语句执行标量函数。 在这种情况下,如果发生错误,则继续执行(除非是批处理中止错误)。 @@ error设置,你可以在函数中检查@@ error的值。 将错误传递给调用者可能会有问题。
我认为最简洁的方法是接受函数可以返回NULL,如果无效的parameter passing。 只要这是明确logging,那么这应该是好的?
-- ============================================= -- Author: AM -- Create date: 03/02/2010 -- Description: Returns the appropriate exchange rate -- based on the input parameters. -- If the rate cannot be found, returns NULL -- (RAISEERROR can't be used in UDFs) -- ============================================= ALTER FUNCTION [dbo].[GetExchangeRate] ( @CurrencyFrom char(3), @CurrencyTo char(3), @OnDate date ) RETURNS decimal(18,4) AS BEGIN DECLARE @ClosingRate as decimal(18,4) SELECT TOP 1 @ClosingRate=ClosingRate FROM [FactCurrencyRate] WHERE FromCurrencyCode=@CurrencyFrom AND ToCurrencyCode=@CurrencyTo AND DateID=dbo.DateToIntegerKey(@OnDate) RETURN @ClosingRate END GO
最好的答案通常是最好的,但不适用于内联表值函数。
MikeTeeVee在他对顶级答案的评论中给出了一个解决scheme,但是它需要使用像MAX这样的聚合函数,这对我的情况并不适用。
我搞砸了一个替代解决scheme的情况下,你需要一个内联表值udf返回类似select *而不是聚合。 解决这个特殊情况的示例代码如下。 正如有人已经指出…… “JEEZ wotta hack” :)我欢迎任何更好的解决scheme,这种情况下!
create table foo ( ID nvarchar(255), Data nvarchar(255) ) go insert into foo (ID, Data) values ('Green Eggs', 'Ham') go create function dbo.GetFoo(@aID nvarchar(255)) returns table as return ( select *, 0 as CausesError from foo where ID = @aID --error checking code is embedded within this union --when the ID exists, this second selection is empty due to where clause at end --when ID doesn't exist, invalid cast with case statement conditionally causes an error --case statement is very hack-y, but this was the only way I could get the code to compile --for an inline TVF --simpler approaches were caught at compile time by SQL Server union select top 1 *, case when ((select top 1 ID from foo where ID = @aID) = @aID) then 0 else 'Error in GetFoo() - ID "' + IsNull(@aID, 'null') + '" does not exist' end from foo where (not exists (select ID from foo where ID = @aID)) ) go --this does not cause an error select * from dbo.GetFoo('Green Eggs') go --this does cause an error select * from dbo.GetFoo('Yellow Eggs') go drop function dbo.GetFoo go drop table foo go
我不能根据davec关于表值函数的回答发表评论,但在我看来这是更简单的解决scheme:
CREATE FUNCTION dbo.ufn_test (@a TINYINT) RETURNS @returns TABLE(Column1 VARCHAR(10), Value1 TINYINT) BEGIN IF @a>50 -- if @a > 50 - raise an error BEGIN INSERT INTO @returns (Column1, Value1) VALUES('error','@a is bigger than 50!') -- reminder Value1 should be TINYINT END INSERT INTO @returns (Column1, Value1) VALUES('Something',@a) RETURN; END SELECT Column1, Value1 FROM dbo.ufn_test(1) -- this is okay SELECT Column1, Value1 FROM dbo.ufn_test(51) -- this will raise an error
有几个人询问在表值函数中引发错误,因为你不能使用“ RETURN [invalid cast] ”types的东西。 将无效的转换赋值给variables也同样适用。
CREATE FUNCTION fn() RETURNS @T TABLE (Col CHAR) AS BEGIN DECLARE @i INT = CAST('booooom!' AS INT) RETURN END
这导致:
转换varchar值“booooom!”时245,级别16,状态1,行14转换失败 数据types为int。
一种方式(一种破解)是有一个执行无效操作的函数/存储过程。 例如,下面的伪SQL
create procedure throw_error ( in err_msg varchar(255)) begin insert into tbl_throw_error (id, msg) values (null, err_msg); insert into tbl_throw_error (id, msg) values (null, err_msg); end;
在表tbl_throw_error上,在err_msg列上有一个唯一的约束。 这个(至less在MySQL上)的副作用是err_msg的值被用作exception的描述,当它返回到应用级exception对象时。
我不知道你是否可以用SQL Server做类似的事情,但值得一试。