如何从SQL Server用户定义的函数报告错误


146

我在SQL Server 2008中编写一个用户定义的函数。我知道这些函数不能以通常的方式引发错误-如果尝试包括RAISERROR语句,则SQL返回:

Msg 443, Level 16, State 14, Procedure ..., Line ...
Invalid use of a side-effecting operator 'RAISERROR' within a function.

但是事实是,该函数接受了一些输入,这可能是无效的,如果是,则该函数没有返回任何有意义的值。那我该怎么办?

我当然可以返回NULL,但是对于使用该函数进行故障排除的任何开发人员而言,这都是困难的。我还可能导致被零除或类似的结果-这将生成错误消息,但会产生误导。有什么办法可以以某种方式报告自己的错误消息?

Answers:


223

您可以使用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.

112
很好的答案,但是JEEZ wotta hack。> :(
JohnL4 2011年

5
对于RETURN是简单选择的内联表值函数,仅此操作不起作用,因为未返回任何内容-甚至不为null,在我的情况下,我想在未找到任何内容时抛出错误。由于明显的性能原因,我不想将内联函数分解为多语句。相反,我使用了您的解决方案以及ISNULL和MAX。现在,RETURN语句如下所示:SELECT ISNULL(MAX(E.EntityID),CAST('Lookup('+ @LookupVariable +')不存在。查找= @ LookupVariable
MikeTeeVee 2012年

是的,您可以引发错误,但是似乎没有条件地引发错误。不管代码路径如何,函数都会执行。
satnhak 2012年

10
很好的解决方案,但是对于那些使用TVF的人来说,这并不容易成为回报的一部分。针对那些用户:declare @error int; set @error = 'Error happened here.';
蒂姆·莱纳

20
我讨厌一千个烈日。没有其他选择吗?精细。但是薄饼……
Remi Despres-Smyth,

18

通常的技巧是强制除以0。这将引发错误并中断正在评估函数的当前语句。如果开发人员或支持人员知道此行为,则对问题进行调查和故障排除非常容易,因为将0除错误理解为另一个不相关问题的征兆。

从任何角度看,这都是很糟糕的,不幸的是,目前SQL函数的设计没有更好的选择。在函数中绝对应该使用RAISERROR。


7

根据弗拉基米尔·科罗廖夫的回答,有条件抛出错误的惯用法是

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)    

6

我认为最干净的方法是只接受如果传递了无效参数则该函数可以返回NULL。只要清楚地证明了这一点,那应该可以吗?

-- =============================================
-- 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

5

RAISEERROR@@ERROR在UDF中不允许使用。您可以将UDF转换为常规程序吗?

摘自Erland Sommarskog的文章SQL Server中的错误处理–背景

用户定义的函数通常作为SET,SELECT,INSERT,UPDATE或DELETE语句的一部分被调用。我发现,如果在多语句表值函数或标量函数中出现错误,该函数的执行将立即中止,该函数所包含的语句也将立即中止。除非错误中止了批处理,否则执行将在下一行继续。在任何一种情况下,@@ error均为0。因此,无法检测到T-SQL的函数中发生了错误。

内联表函数不会出现此问题,因为内联表值函数基本上是查询处理器粘贴到查询中的宏。

您还可以使用EXEC语句执行标量函数。在这种情况下,如果发生错误(除非是中止批处理的错误),则继续执行。设置了@@ error,您可以在函数中检查@@ error的值。但是,将错误传达给调用方可能会有问题。


4

最佳答案通常是最好的,但不适用于内联表值函数。

MikeTeeVee在对最佳答案的评论中给出了解决方案,但是它需要使用像MAX这样的聚合函数,在我的情况下效果不佳。

当您需要一个值为udf的内联表时,它会返回类似于select *的值,而不是一个聚合值。解决此特定情况的示例代码如下。正如有人已经指出的那样…… “ JEEZ wotta hack” :)我欢迎对此案有任何更好的解决方案!

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

1
对于阅读的任何人,我都没有考虑潜在的性能影响...如果hack union + case语句使事情放慢速度,我也不会感到惊讶...
davec

4

一些人正在询问有关表值函数中的错误的问题,因为您不能使用“ RETURN [invalid cast] ”之类的东西。将无效的强制转换分配给变量也可以。

CREATE FUNCTION fn()
RETURNS @T TABLE (Col CHAR)  
AS
BEGIN

DECLARE @i INT = CAST('booooom!' AS INT)  

RETURN

END

结果是:

消息245,级别16,状态1,第14行转换varchar值'booooom!'时转换失败。数据类型为int。


2

我无法评论davec关于表值函数的问题,但以我的拙见,这是更简单的解决方案:

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

-3

一种方法(破解)是使函数/存储过程执行无效的操作。例如下面的伪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列上有唯一约束。这样做的副作用(至少在MySQL上是如此)是,当err_msg的值返回到应用程序级异常对象时,该值将用作异常的描述。

我不知道您是否可以使用SQL Server做类似的事情,但是值得一试。


5
有趣的想法,但是函数中也不允许INSERT。
EMP
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.