执行存储过程参数验证的“正确”方法

时间:2009-06-29 12:10:40

标签: sql sql-server stored-procedures error-handling

我有一个执行某些参数验证的存储过程,如果参数无效,则会失败并停止执行。

我的第一个错误检查方法如下:

create proc spBaz
(
  @fooInt int = 0,
  @fooString varchar(10) = null,
  @barInt int = 0,
  @barString varchar(10) = null
)
as
begin
  if (@fooInt = 0 and (@fooString is null or @fooString = ''))
    raiserror('invalid parameter: foo', 18, 0)

  if (@barInt = 0 and (@barString is null or @barString = ''))
    raiserror('invalid parameter: bar', 18, 0)

  print 'validation succeeded'
  -- do some work
end

由于严重性18不会停止执行并且“验证成功”与错误消息一起打印,因此无法解决问题。

我知道我可以在每次raiserror之后添加一个回报,但这对我来说看起来很难看:

  if (@fooInt = 0 and (@fooString is null or @fooString = ''))
  begin
    raiserror('invalid parameter: foo', 18, 0)
    return
  end

  ...

  print 'validation succeeded'
  -- do some work

由于在try / catch块中捕获了严重性为11或更高的错误,我测试的另一种方法是将我的错误检查封装在这样的try / catch块中。问题是吞没了错误而根本没有发送给客户端。所以我做了一些研究,找到了rethrow错误的方法:

  begin try
    if (@fooInt = 0 and (@fooString is null or @fooString = ''))
      raiserror('invalid parameter: foo', 18, 0)

    ...
  end try
  begin catch
    exec usp_RethrowError
    return
  end catch

  print 'validation succeeded'
  -- do some work

我对这种方法仍然不满意,所以我问你:

您的参数验证如何?是否有某种“最佳实践”来进行这种检查?

5 个答案:

答案 0 :(得分:45)

我认为没有一种“正确”的方法可以做到这一点。

我自己的偏好与第二个示例相似,但每个参数都有一个单独的验证步骤,并且有更明确的错误消息。

正如你所说,它有点麻烦和丑陋,但代码的意图对于阅读它的人来说是显而易见的,并且它完成了工作。

IF (ISNULL(@fooInt, 0) = 0)
BEGIN
    RAISERROR('Invalid parameter: @fooInt cannot be NULL or zero', 18, 0)
    RETURN
END

IF (ISNULL(@fooString, '') = '')
BEGIN
    RAISERROR('Invalid parameter: @fooString cannot be NULL or empty', 18, 0)
    RETURN
END

答案 1 :(得分:1)

我们通常会避免使用raiseerror()并返回一个表示错误的值,例如负数:

if <errorcondition>
    return -1

或者将结果传递给两个参数:

create procedure dbo.TestProc
    ....
    @result int output,
    @errormessage varchar(256) output
as
set @result = -99
set @errormessage = null
....
if <errorcondition>
    begin
    set @result = -1
    set @errormessage = 'Condition failed'
    return @result
    end

答案 2 :(得分:1)

从这个答案历史中可以看出,我按照这个问题接受了答案,然后继续“发明”一个与你的第二个方法基本相同的解决方案。

咖啡因是我的主要能量来源,因为我花了大半生的时间半睡半醒,因为我花了太多时间编码;因此,在你正确地指出它之前,我没有意识到我的失礼。

因此,为了记录,我更喜欢你的第二种方法:使用SP来引发当前错误,然后在参数验证周围使用TRY / CATCH。

它减少了对所有IF / BEGIN / END块的需求,因此减少了行数,并将重点放在验证上。在阅读SP的代码时,能够看到对参数执行的测试非常重要;在我看来,满足SQL解析器的所有额外语法绒毛都会妨碍。

答案 3 :(得分:0)

我希望尽快退回,并且不要指出在程序结束时从一个点返回所有内容。几年前,我接受了这种习惯。另外,我总是返回一个值:

RETURN 10

应用程序将在正数上显示致命错误,并在负值上显示用户警告消息。

我们总是使用错误消息的文本传回OUTPUT参数。

示例:

IF ~error~
BEGIN
    --if it is possible to be within a transaction, so any error logging is not ROLLBACK later
    IF XACT_STATE()!=0
    BEGIN
        ROLLBACK
    END

    SET @OutputErrMsg='your message here!!'
    INSERT INTO ErrorLog (....) VALUES (.... @OutputErrMsg)
    RETURN 10

END

答案 4 :(得分:0)

我总是使用参数@Is_Success位作为OUTPUT。所以,如果我有错误,那么@ Is_success = 0。当父过程检查@ Is_Success = 0然后它回滚其事务(带子事务)并从@Error_Message向客户端发送错误消息。