logo SQL Prompt教程 我也要发布文档

SQL语法提示工具SQL Prompt教程:忽略使用或滥用RETURN关键字(BP016)


SQL Prompt是一款实用的SQL语法提示工具。SQL Prompt根据数据库的对象名称、语法和代码片段自动进行检索,为用户提供合适的代码选择。自动脚本设置使代码简单易读--当开发者不大熟悉脚本时尤其有用。SQL Prompt安装即可使用,能大幅提高编码效率。此外,用户还可根据需要进行自定义,使之以预想的方式工作。

点击下载SQL Prompt免费版

文章解释了在存储过程或批处理中正确使用RETURN关键字,将非零RETURN代码传递给调用进程,并通知它错误,还解释了一些误用。

所有存储过程、语句块和批处理都返回一个记录其执行成功的代码。如果批处理或过程到达结尾,它将自动返回0(零),这意味着成功,除非您使用RETURN关键字指定,否则使用整数参数。除了0之外,没有为任何数字定义任何东西,但约定存在任何其他数字都表示某些描述失败。如果发生错误,您应该捕获返回的值并将其发送到调用进程,以便它可以相应地做出响应。

仅使用RETURN值来传达流程的成功或失败,永远不要将值作为流程的一部分返回,例如某一天的购买数量。此外,存储过程或批处理应该永远不会有没有值的RETURN关键字,如果SQL Prompt 检测到此错误,它将发出BP016警告。

55word-image-13.png

什么是RETURN关键字,它返回什么?

要从报表任何问题的任何存储过程或批处理返回值,您需要将其分配给变量并使用RETURNcontrol-of-flow关键字,并将值作为参数。此RETURN将立即终止批处理的执行,并返回您作为参数传递的值。如果尝试使用RETURN关键字从存储过程返回NULL,则会收到警告,并返回0。如果某个过程遇到需要立即终止的错误,它将返回NULL,因为它永远不会到达RETURN关键字或批处理的末尾!如果批处理或过程到达结尾,它将自动返回零。

某些系统存储过程会返回运行批处理时发生的实际错误代码,包括程序中RAISERROR语句中指定的那些代码,但是没有标准表明您需要执行此操作。实际上,文档建议您可以根据执行过程的错误将任意值传递回调用批处理。

我们可以证明,即使sp_ExecuteSQL返回由错误生成的错误代码,也会返回0,如果它只是一个警告。如果它只是一个警告,它会继续执行,当然,如果它随后成功则其返回代码为0,如果不成功则返回失败的错误代码。

raiserror('HELP, I''m trapped in this batch!',5,1)
/*
Msg 50000, Level 5, State 1, Line 25
HELP, I'm trapped in this batch!
*/
DECLARE @Return int
EXECUTE @Return= sp_executeSQL  N'raiserror(''HELP, I''''m trapped in this batch!'',16,1)'
SELECT @Return
--returns 50000 (user-defined error). But what if we do a warning instead of an error?
DECLARE @Return int
EXECUTE @Return= sp_executeSQL  N'raiserror(''HELP, I''''m trapped in this batch!'',5,1)'
SELECT @Return
--returns 0 because it was only a warning
SELECT * FROM dbo.MissingTable
/*
Msg 208, Level 16, State 1, Line 40
Invalid object name 'dbo.MissingTable'.
*/
DECLARE @Return int
EXECUTE @Return= sp_executeSQL  N'SELECT * FROM dbo.MissingTable'
SELECT @Return
--returns 208

虽然我们大多数人日常编写的简单存储过程不需要太多使用RETURN代码,但是当我们开始执行更复杂的基于事务的处理时,这样做的价值很快就会出现。

让我们从返回代表错误号的代码开始。

CREATE PROCEDURE #TryoutProcedure
AS
  BEGIN
    BEGIN TRY
      SELECT 1 / 0; --deliberately trigger a divide by zero
    END TRY
    BEGIN CATCH
      RETURN Error_Number(); --return the error
    END CATCH;
  END;
GO
DECLARE @Return INT;
EXECUTE @Return = #TryoutProcedure; --execute our sample procedure
SELECT @Return
IF Coalesce(@Return,0) <> 0
  SELECT * FROM sys.messages --and see if the error was passed back
     WHERE message_id = @Return AND language_id = 1033;

56word-image-14.png

您会注意到在程序结束时无需添加RETURN 0,因为这是自动完成的。如果到达批处理的末尾,SQL Server会认为您已赢了,因此返回0。如果您尝试执行...

SELECT * FROM dbo.MissingTable

…代替…

SELECT 1 / 0;

...你会发现返回NULL以及“无效的对象名”错误。为什么?它放弃了程序而不是遵守TRY…CATCH构造。这是因为我们在语句级重新编译期间触发了对象名称解析错误(由于延迟名称解析,所以存储过程无故障编译)。SQL Server无法从此错误中恢复批处理并使用a完全中止执行NULL。正如福尔摩斯所说,这些都是深水。更确切地说:

  • TRY…CATCH 不会捕获严重性为10或更低的警告或信息性消息。

  • TRY…CATCH只能在正在运行的过程中运行。这意味着,例如,无法捕获严重性为20或更高的错误,这些错误会阻止会话的SQL Server数据库引擎任务处理。这也适用于注意事项,例如客户端中断请求或客户端连接中断,以及系统管理员使用该KILL语句结束会话时。如果存在编译错误(例如语法错误),这会阻止批处理运行,那么它将永远不会到达TRY …CATCH语句。如果在任何重新编译期间解析对象名称时出错,也会发生这种情况。

Code Smells和RETURN值

RETURN值只应用于表示所执行操作的成功或失败,以及其原因。但是,在OUTPUT参数之前有一段时间,该RETURN值是将任何类型的整数值传递回批处理的唯一简单方法。

CREATE PROCEDURE #HowManylettersInWord
@AString nvarchar(2000)
AS
/* never do this. This is a code smell */
  BEGIN
  RETURN (PATINDEX('%[^-a-z]%',@AString+'|' COLLATE Latin1_General_CI_AI)) 
  END;
/* tempting. If only the correct way was as slick! */
GO
DECLARE @letters int
EXECUTE @letters=  #HowManylettersInWord 'predestination and science'; --execute our sample procedure
SELECT @letters
EXECUTE @letters=  #HowManylettersInWord 'level-crossing gates'; --execute our sample procedure
SELECT @letters

当被逼到角落时,任何灰色的数据库开发人员都会承认使用RETURN代码执行此操作。现在我们没有必要对这个SQL Code Smell视而不见。当您从一个过程传递值时,您可以在丰富的数据类型中拥有任意数量的OUTPUT参数,并以一种即使是最无聊或最缺乏经验的团队成员都可以找到的方式命名它们。

但是,最好保持返回错误和问题的惯例,RETURN值是显而易见的。将存在与错误值对应的正整数,如果过程无法恢复,失败则为NULL,或者对于应用程序级进程问题为负值。

在典型的批处理中,几个存储过程按顺序执行,但控制流程根据每个过程中发生的情况而变化。可能会发生不好的事情,您需要做出相应的反应。举个例子,我们假设一个插入表中的过程;如果进程失败,它需要返回适当的值。例如,如果结果是重复条目,则该过程应相应地向调用批处理报表,解释违反业务规则的情况。但是,它可能由于完全不同的原因而失败,例如死锁或磁盘空间已用完。这些问题中的每一个可能需要针对调用批处理或应用程序的不同解决方案,尝试插入的过程只需要返回相应的错误。由程序决定相应的反应。

作为对RETURN传回的错误作出反应的一个例子,有一个不幸的情况是你的进程被选为死锁牺牲品:

'Transaction (Process ID %d) was deadlocked on {%Z} resources with another process and has been chosen as the deadlock victim. Rerun the transaction' (Msg 1205).

当然,它实际上应该会说,“稍等一会儿,然后重新运行事务”。在处理偶尔容易出现死锁的进程时,启动事务、调用过程、在程序的RETURN中捕获错误1205 ,回滚事务,等待一小段时间并重试。

RETURN代码的另一个用途是返回应用程序“流程”问题的负数,例如“客户当前已暂停”、“超出信用额度”、“帐户上的文件备注”或“拒绝银行转帐”。虽然为SQL Server错误保留了正数,但您可以使用负数表示应用程序进程错误。

这是一个简单的示例,用于查看数据库中是否存在城市。它使用正数表示SQL Server错误,使用负数表示流程问题(这个例子中的城市不存在)。这些流程问题通常在应用程序中处理得最好,因此返回一个整数并让应用程序处理反应(例如表单上的提示,使用适当的语言)要简单得多。

USE adventureworks2016
GO
CREATE PROCEDURE #CheckContactCity  
   (@cityName VARCHAR(50))  
AS  
BEGIN 
   DECLARE @CityExists int
   BEGIN try
     SELECT @CityExists = CASE when EXISTS (SELECT * 
       FROM adventureworks2016.Person.Address  
         WHERE City = @cityName) THEN 1 ELSE 0 end
   END TRY
   BEGIN CATCH
     RETURN Error_Number(); --return the error as a positive integer
   END CATCH
   IF @CityExists= 0 RETURN -100 --you've chosen this to mean 'city doesn't exist 
END  
Go
--now test it out
DECLARE @Return INT;
EXECUTE @Return = #CheckContactCity 'Denver'; --execute our sample procedure
SELECT @Return
--returns zero 'city does exist'
EXECUTE @Return = #CheckContactCity 'fougasse'; --execute our sample procedure
SELECT @Return
--returns -100 'city doesn't exist

对于您漂亮、整洁的代码来说,这一切看起来都有些混乱,但是在程序正文中您需要的唯一RETURN关键字是指示失败的那些,除非你希望在某个时候中止程序,因为没有更多的事情可做取得成功。如果一个程序到达END,它就赢了,所以自动返回零而不需要告诉它。

结论

存储过程应通知调用它的进程是否成功。存储过程返回一个整数值,应该由调用它的SQL批处理或应用程序捕获和检查。成功由零(0)表示。

但是,成功可能意味着许多事情。一个过程可以完全没有错误,但它可能在业务流程方面失败了。按照惯例,返回值中的正数是SQL Server错误的消息ID,您可以自由地为您遇到的任何应用程序进程问题分配负值。

与RETURN相关的四个SQL代码气味,换句话说,值得检查或审查的编码实践:

1、发生错误时,不会传回非零RETURN代码以通知调用方存储过程。

2、如果没有整数参数,则使用RETURN关键字。 (BP0016)

3、发生错误时,无法对存储过程返回的值做出适当的响应。

4、使用RETURN作为流程的一部分传递值,例如给定日期的购买数量,而不是流程的成功或失败。


想要购买SQL Prompt正版授权,或了解更多产品信息请点击“咨询在线客服”

扫描关注慧聚IT微信公众号,及时获取最新动态及最新资讯

1563778777.jpg