SQL語法提示工具SQL Prompt——忽略使用或濫用RETURN關鍵字(BP016)

SQL Prompt是一款實用的SQL語法提示工具。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(); <a id="post-505042-_Hlk15977359"></a>--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作爲流程的一部分傳遞值,例如給定日期的購買數量,而不是流程的成功或失敗。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章