(《SQL Server 2005 編程入門經典》 第12章)
存儲過程(stored procedure)有時也稱為sproc。存儲過程存儲於數據庫中而不是在單獨的文件中,有輸入參數、輸出參數以及返回值等。
12.1 創建存儲過程:基本語法
在數據庫中,創建存儲過程和創建其他對象的過程一樣,除了它使用的AS關鍵字外。存儲過程的基本語法如下:
CREATE PROCDUER|PROC <sproc name> [<parameter name>[schema.]<data type>[VARYING][=<default value>][OUT [PUT]][, [<parameter name>[schema.]<data type>[VARYING][=<default value>][OUT [PUT]][, ...]] [WITH RECOMPILE|ENCRYPTION|[EXECUTE AS {CALLER|SELF|OWNER|<'user name'>}] [FOR REPLICATION] AS <code>|EXTERNAL NAME <assembly name>.<assembly class>
基礎存儲過程的示例
創建存儲過程的代碼如下:
USE Northwind GO CRREATE PROC spShippers AS SELECT * FROM Shippers
執行存儲過程:
EXEC spShippers
12.2 使用ALTER改變存儲過程
當使用T-SQL編輯存儲過程的時候,需要記住的是它完全取代了現存的存儲過程。使用ALTER PROC和CREATE PROC的區別在於:
- ALTER PROC期望找到現存的存儲過程,而CREATE則不是。
- ALTER PROC保留了已經建立的存儲過程的任何權限。它在系統對象中保留了相同的對象ID並允許保留依賴關系。
- ALTER PROC在其他對象上保留了任何依賴關系的信息,這些對象可以調用修改的存儲過程。
注意:
如果執行DROP,然后使用CREATE,這和使用ALTER PROC語句一樣,幾乎都能得到相同的效果,除了一個很重要的區別——如果使用DROP和CREATE,則需要完全重新建立權限,權限規定了可以使用以及不能使用存儲過程的用戶。
12.3 刪除存儲過程
這個過程非常簡單:
DROP PROC|PROCEDURE <sproc name>
12.4 參數化(Parameterization)
聲明參數
聲明參數需要以下2到4部分信息:
- 名稱
- 數據類型
- 默認值
- 方向
語法如下:
@parameter_name [AS] datatype[= default|NULL] [VARYING] [OUTPUT|OUT]
名稱有一個簡單的規則集合。首先,它必須以@開始。此外,命名規則除了不能有嵌套的空格外,它和SQL的命令規則是相同的。
數據類型可以使用SQL Server內置的或用戶自定義的類型。
注意:
- 聲明CURSOR類型參數的時候,必須也使用VARYING和OUTPUT選項。
- OUTPUT可以簡寫為OUT。
示例:
USE Northwind GO CREATE PROC spInsertShipper @CompanyName NVARCHAR(40), @Phone NVARCHAR(24) AS INSERT INTO Shippers VALUES (@CompanyName, @Phone)
可以使用這個新的存儲過程來插入新的數據:
EXEC spInstertShipper 'Speedy Shippers, Inc.', '(503)555-5566'
因為並沒有為任何參數提供默認值,所以需要提供兩個參數。這意味着為了成功運行該存儲工程,則必須提供兩個參數。
1. 提供默認值
為了使參數是可選的,可以提供默認值。示例:
USE Northwind GO CREATE PROC spInsertShipperOptionalPhone @CompanyName NVARCHAR(40), @Phone NVARCHAR(24) = NULL AS INSERT INTO Shippers VALUES (@CompanyName, @Phone)
重新發出命令,但是使用新的存儲過程:
EXEC spInsertShipperOptionalPhone 'Speedy Shippers, Inc'
這次一切順利,成功插入了新的紀錄。
2. 創建輸出參數
示例:
USE Northwind GO CREATE PROC spInsertOrder @CustomerID NVARCHAR(5), @EmployeeID INT, @OrderDate DATETIME = NULL, @RequiredDate DATETIME = NULL, @ShippedDate DATETIME = NULL, @ShipVia INT, @Freight MONEY, @ShipName NVARCHAR(40) = NULL, @ShipAddress NVARCHAR(60) = NULL, @ShipCity NVARCHAR(15) = NULL, @ShipRegion NVARCHAR(15) = NULL, @ShipPostalCode NVARCHAR(10) = NULL, @ShipCountry NVARCHAR(15) = NULL, @OrderID INT OUTPUT AS INSERT INTO Orders VALUES ( @CustomerID, @EmployeeID, @OrderDate, @RequiredDate, @ShippedDate, @ShipVia, @Freight, @ShipName, @ShipAddress, @ShipCity, @ShipRegion, @ShipPostalCode, @ShipCountry ) SELECT @OrderID = @@IDENTITY
執行該存儲過程的代碼如下:
USE Northwind GO DECLARE @MyIdent INT EXEC spInsertOrder @CustomerID = 'ALFKI', @EmployeeID = 5, @OrderDate = '5/1/1999' @ShipVia = 3, @Freight = 5.00, @OrderID = @MyIdenty OUTPUT SELECT @MyIdent AS IdentityValue SELECT OrderID, CustomerID, EmployeeID, OrderDate, ShipName FROM Orders WHERE OrderID = @MyIdent
需要注意以下幾點:
- 在存儲過程聲明中,輸出參數需要使用OUTPUT關鍵字。
- 調用存儲過程的時候也必須使用OUTPUT關鍵字,才能保證參數被正確的輸出。注意如果沒有使用OUTPUT關鍵字,不會產生任何錯誤,但是此時輸出參數的值將是無法保證的。
- 賦給輸出變量的變量不需要和存儲過程中的內部參數擁有相同的名稱。例如在本例中,內部參數叫做@OrderID,而傳給值的變量叫做@MyIdent。
- 需要使用EXEC(或EXECUTE)關鍵字來調用存儲過程。
12.5 流控制語句
T-SQL提供了大多數流控制語句的典型的選擇,包括:
- IF...ELSE
- GOTO
- WHILE
- WAITFOR
- TRY/CATCH
同樣也有CASE語句,但是它沒有像其他語言中預期的那種流控制級的能力。
12.5.1 IF...ELSE語句
IF...ELSE語句的實現方式和C是接近相同的。基本的語法如下:
IF <Boolean Expression> <SQL statement> | BEGIN <code series> END [ELSE <SQL statement> | BEGIN <code series> END]
其中的表達式可以是取布爾值的任意表達式。
提示:
不恰當的使用NULL值是個常見的陷阱。例如經常會有如下錯誤出現:
IF @MyVar = NULL
在大多數系統上(遵循ANSI標准)這樣的表達式永遠都不會為真,並且為繞過所有的NULL值結束。想要判斷一個值是否為空應該這樣來寫:
IF @MyVar IS NULL
不要忘記了NULL不等於任何值——甚至是NULL。不要使用"="而要使用"IS"。
DATEDIFF函數
DATEDIFF的語法如下:
DATEDIFF (<datepart>, <start date>, <end date>)
DATEDIFF可以比較日期型數據的任意部分,可以從年到毫秒。start date和end date參數是合法的日期表達式。datepart參數可以是下列的值:
datepart |
縮寫 |
年 |
year, yy, yyyy |
季度 |
quarter,qq, q |
月 |
month, mm, m |
星期 |
week, dw, w |
日 |
day, dd, d |
時 |
hour, hh |
分 |
minute, mi, n |
秒 |
second, ss, s |
毫秒 |
millisecond, ms |
1. ELSE子句
注意:
結果返回值為NULL的表達式會被當作FALSE從而進入ELSE子句。也就是說,如果IF子句中的語句返回值為FALSE或者NULL,則執行ELSE子句中的語句。
2. 從DATETIME字段中截取時間
為了能截取日期信息,要么把日期分成多個部分,然后不帶時間地進行重組,要么可以使用CONVERT函數,該函數能把它轉換為不帶時間的日期,並且也能把它轉換回來。
CONVERT()原來是SQL Server中唯一一個用來在數據類型之間轉換數據的方法。現在,CAST()復制了它的大部分功能,並且是兼容ANSI的。然而,CONVERT()還是有一些特殊的日期格式化處理的能力,這些是CAST所不具備的。
CONVERT的語法如下:
CONVERT (<target data type>, <expression to be converted>, <style>)
前兩個參數簡單明了,最后一個參數只應用於處理時期的時候,其目的是告訴SQL Server需要的日期格式。這些普通日期格式的示例包括1,這是美國mm/dd/yy格式的標准;以及12,這是ISO格式(yymmdd)。給任意的格式加上100會給日期模式加入整個世紀的時間(即年份用4位數表示,例如101樣式為mm/dd/yyyy)。
例如,GETDATE函數類似於如下:
SELECT CONVERT(DATETIME, (CONVERT(VARCHAR, GETDATE(), 112))
這樣會處理ANSI的日期格式並能再次轉換回來。
3. 在存儲過程中實現ELSE語句
USE Northwind GO ALTER PROC spInsertOrder @CustomerID NVARCHAR(5), @EmployeeID INT, @OrderDate DATETIME = NULL, @RequiredDate DATETIME = NULL, @ShippedDate DATETIME = NULL, @ShipVia INT, @Freight MONEY, @ShipName NVARCHAR(40) = NULL, @ShipAddress NVARCHAR(60) = NULL, @ShipCity NVARCHAR(15) = NULL, @ShipRegion NVARCHAR(15) = NULL, @ShipPostalCode NVARCHAR(10) = NULL, @ShipCountry NVARCHAR(15) = NULL, @OrderID INT OUTPUT AS DECLARE @InsertedOrderDate SMALLDATETIME IF DATEDIFF(dd, @OrderDate, GETDATE()) > 7 SELECT @InsertedOrderDate = NULL ELSE SELECT @InsertedOrderDate = CONVERT(DATETIME, CONVERT(VARCHAR, @OrderDate, 112)) INSERT INTO Orders VALUES ( @CustomerID, @EmployeeID, @OrderDate, @RequiredDate, @ShippedDate, @ShipVia, @Freight, @ShipName, @ShipAddress, @ShipCity, @ShipRegion, @ShipPostalCode, @ShipCountry ) SELECT @OrderID = @@IDENTITY
4. 把代碼分組為塊
SQL Server提供了把代碼分組為塊的方法,可以認為這個塊是屬於一起的。這個塊以BEGIN語句開始,然后直到END語句結束。塊類似如下:
IF <expression> BEGIN Statement that executes if expression is TRUE Additional statements ... ... Still going with statements from TRUE expression IF <expression> BEGIN Statement that executes if both outside and inside expression is true Additional statements ... ... Still statements from both TRUE expressions END Out of the condition from inner condition, but still part of first block END ELSE BEGIN Statement that executes if expression is FALSE Additional statements ... ... Still going with statements from FLASE expression END
現在可以修改訂單插入的存儲過程如下:
USE Northwind GO ALTER PROC spInsertOrder @CustomerID NVARCHAR(5), @EmployeeID INT, @OrderDate DATETIME = NULL, @RequiredDate DATETIME = NULL, @ShippedDate DATETIME = NULL, @ShipVia INT, @Freight MONEY, @ShipName NVARCHAR(40) = NULL, @ShipAddress NVARCHAR(60) = NULL, @ShipCity NVARCHAR(15) = NULL, @ShipRegion NVARCHAR(15) = NULL, @ShipPostalCode NVARCHAR(10) = NULL, @ShipCountry NVARCHAR(15) = NULL, @OrderID INT OUTPUT AS DECLARE @InsertedOrderDate SMALLDATETIME IF DATEDIFF(dd, @OrderDate, GETDATE()) > 7 BEGIN SELECT @InsertedOrderDate = NULL PRINT 'Invalid Order Date' PRINT 'Supplied Order Date was greater than 7 days old.' PRINT 'The value has been reset to NULL' ELSE BEGIN SELECT @InsertedOrderDate = CONVERT(DATETIME, CONVERT(VARCHAR, @OrderDate, 112)) PRINT 'The time of Day in Order Date was truncated' END INSERT INTO Orders VALUES ( @CustomerID, @EmployeeID, @OrderDate, @RequiredDate, @ShippedDate, @ShipVia, @Freight, @ShipName, @ShipAddress, @ShipCity, @ShipRegion, @ShipPostalCode, @ShipCountry ) SELECT @OrderID = @@IDENTITY
12.5.2 CASE語句
CASE語句在某種程度上與一些編程語言中的一些不同語句是等價的。例如:
-
C、C++、Delphi中的switch
-
Visual Basic中的select case
-
COBOL中的evaluate
在T-SQL中使用CASE語句的一個很大的缺點是:在很多方面,它更像替換運算符而非流控制語句。編寫CASE語句的方式不只一種——可以使用輸入表達式或者布爾表達式。第一種方法是使用一個輸入表達式來與每個WHEN子句中用到的值進行比較。SQL Server文檔把這種方法稱為簡單CASE:
CASE <input expression> WHEN <when expression> THEN <result expression> [...n] [ELSE <result expression>] END
第二種方法將提供一個表達式,其中每個WHEN子句的值將為TRUE或者FALSE。相關文檔把它稱為搜索CASE:
CASE WHEN <Boolean expression> THEN <result expression> [...n] [ELSE <result expression>] END
可以使用CASE語句最好的方式是把它與SELECT語句放一起使用。
1. 簡單CASE
簡單CASE使用結果等於布爾值的表達式。示例:
USE Northwind GO SELECT TOP 10 OrderID, OrderID % 10 AS 'Last Digit', Position = CASE OrderID % 10 WHEN 1 THEN 'First' WHEN 2 THEN 'Second' WHEN 3 THEN 'Third' WHEN 4 THEN 'Fourth' ELSE 'Something Else' END FROM Orders
2. 搜索CASE
搜索CASE語句和簡單CASE語句非常相同,它只有兩個很細微的不同點:
-
沒有輸入表達式。
-
WHEN表達式必須為布爾值。
示例:
USE Northwind GO SELECT TOP 10 OrderID % 10 AS "Last Digit", ProductID, "How Close?" = CASE WHEN (OrderID % 10) < 3 THEN 'Ends with less than three' WHEN ProductID = 6 THEN 'ProductID is 6' WHEN ABS(OrderID % 10 - ProductID) <= 1 THEN 'Within 1' ELSE 'More than one apart' END FROM OrderDetails WHERE ProductID < 10 ORDER BY OrderID DESC
注意SQL Server求值的工作方式:
-
即使兩個條件都為真,但只使用第一個條件。
-
不需要使用"break"語句,在一個條件滿足后自動終止。
-
可以在條件表達式中混合和匹配正在使用的字段。
-
只要最后等於布爾值的結果,則可以執行任何表達式。
示例:
USE Northwind GO CREATE PROC spMarkupTest @MarkupAsPercent MONEY AS DECLARE @Multiplier MONEY SELECT @Multiplier = @MarkupAsPerent / 100 + 1 SELECT TOP 10 ProductID, ProductName, UnitPrice, UnitPrice * @Multiplier AS "Markuped Up Price", "New Price" = CASE WHEN FLOOR(UnitPrice * @Multiplier + .24) > FLOOR(UnitPrice * @Multiplier) THEN FLOOR(UnitPrice * @Multiplier) + .95 WHEN FLOOR(UnitPrice * @Multiplier + .5) > FLOOR(UnitPrice * @Multiplier) THEN FLOOR(UnitPrice * @Multiplier) + .75 ELSE FLOOR(UnitPrice * @Multiplier) + .49 END FROM Products ORDER BY ProductID DESC
執行該存儲過程:
EXEC spMarkupTest 10
12.5.3 使用WHILE語句循環
語法如下:
WHILE <boolean expression> <sql statement> | [BEGIN <statement block> [BREAK] <sql statement>|<statement block> [CONTINUE[ END]
在WHILE語句中必須跟上BEGIN...END,其中包含整個語句塊。
12.5.4 WAITFOR語句
使用WAITFOR語句可以讓SQL Server來做等待工作。它的語法也很簡單:
WAITFOR DELAY <time> WAITFOR TIME <time>
1. DELAY參數
DELAY參數指定等待的總時間。但是不能指定天數,而只能是小時、分鍾和秒數。允許延時的最大值為24小時。示例:
WAITFOR DELAY '01:00'
將會等待一個小時,之后再運行WAITFOR后面的代碼。
2. TIME參數
TIME參數指定了需要等待的具體的時間。同樣,不能指定日期作為參數,而只能是24小時內的時間。例如:
WAITFOR TIME '01:00'
將會等到凌晨1點,之后再運行WAITFOR后面的代碼。
12.5.5 TRY/CATCH塊
簡而言之,如果代碼沒有任何類型的異常,或者錯誤級別是10或者10以下的話,則會根據TRY塊執行代碼。但是,一旦代碼出現的錯誤超過了10(11或者更高)的話,則會馬上轉移到CATCH塊中來。
12.6 通過返回值確認成功或失敗
返回值指示了存儲過程的成功或者失敗,甚至是成功或失敗的范圍或屬性。
RETURN的工作方式
事實上,不管是否提供返回值,程序都會收到一個返回值。SQL Server默認地會在完成存儲過程時自動返回一個0值。
為了從存儲過程向調用代碼返回值,只需要使用RETURN語句:
RETURN [<integer value to return>]
注意:
返回值必須是整數。
RETURN語句是無條件地從存儲過程中退出的。
示例:
USE Northwind GO CREATE PROC spTestReturns AS DECLARE @MyMessage VARCHAR(50) DECLARE @MyOtherMessage VARCHAR(50) SELECT @MyMessage = 'Hi, it''s that line before the RETURN' PRINT @MyMessage RETURN SELECT @MyOtherMessage = 'Sorry, but we won''t get this far' PRINT @MyOtherMessage RETURNUSE Northwind GO CREATE PROC spTestReturns AS DECLARE @MyMessage VARCHAR(50) DECLARE @MyOtherMessage VARCHAR(50) SELECT @MyMessage = 'Hi, it''s that line before the RETURN' PRINT @MyMessage RETURN SELECT @MyOtherMessage = 'Sorry, but we won''t get this far' PRINT @MyOtherMessage RETURN為了能獲取RETURN語句的值,需要在EXEC語句中把值賦給變量。
DECLARE @Return INT EXEC @Return = spTestReturns SELECT @Return
直接RETURN會默認返回0,需要返回其他整數可以直接寫RETURN <integer>。
12.7 處理錯誤
在SQL Server中可能會發生3種常見的錯誤類型:
-
會產生運行時錯誤並終止代碼運行的錯誤。
-
SQL Server知道的錯誤,但是它不產生使代碼停止運行的運行時錯誤。這些錯誤也可以成為內聯錯誤。
-
在邏輯上很明顯但在SQL Server中不太引起注意的錯誤。
12.7.1 以前的方式
以前的方式需要在代碼中監視錯誤的條件,然后決定在檢測到錯誤的時候(很可能是在發生實際錯誤過后)要做的處理。
1. 處理內聯的錯誤
內聯錯誤是指那些能在SQL Server中繼續運行,但是因為某種原因而不能成功完成指定任務的錯誤。例如,違反外鍵約束的插入。SQL Server會拒絕執行該語句並輸出錯誤信息,但不會終止代碼運行。
例如:
USE Northwind GO INSERT INTO OrderDetails (OrderID, ProductID, UnitPrice, Quantity, Discount) VALUES (999999, 11, 10.00, 0)
SQL Server會拒絕執行該插入,並輸出錯誤信息:
Msg 547, Level 16, State 0, Line 2
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_Order_Details_Orders".The conflict occurred in database "Northwind", table
"Orders", column 'OrderID'.
The statement has been terminated.
注意上面的547錯誤——這是我們可以利用的錯誤。
2. 利用@@ERROR
@@ERROR包含了最后的T-SQL語句執行的錯誤號。如果該值為0,則表示沒有發生錯誤。
說明:
每個新語句都會使@@ERROR復位。
示例:
USE Northwind GO DECLARE @Error INT INSERT INTO OrderDetails (OrderID, ProductID, UnitPrice, Quantity, Discount) VALUES (999999, 11, 10.00, 0) SELECT @Error = @@ERROR PRINT '' PRINT 'The Value of @Error is ' + CONVERT(VARCHAR, @Error) PRINT 'The Value of @@ERROR is ' + CONVERT(VARCHAR, @@ERROR)返回結果:
Msg 547, Level 16, State 0, Line 2
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_Order_Details_Orders".The conflict occurred in database "Northwind", table
"Orders", column 'OrderID'.
The statement has been terminated.
The Value of @Error is 547
The Vaule of @@ERROR is 0
3. 在存儲過程中使用@@ERROR
USE Northwind GO ALTER PROC spInsertOrder @CustomerID NVARCHAR(5), @EmployeeID INT, @OrderDate DATETIME = NULL, @RequiredDate DATETIME = NULL, @ShippedDate DATETIME = NULL, @ShipVia INT, @Freight MONEY, @ShipName NVARCHAR(40) = NULL, @ShipAddress NVARCHAR(60) = NULL, @ShipCity NVARCHAR(15) = NULL, @ShipRegion NVARCHAR(15) = NULL, @ShipPostalCode NVARCHAR(10) = NULL, @ShipCountry NVARCHAR(15) = NULL, @OrderID INT OUTPUT AS DECLARE @InsertedOrderDate SMALLDATETIME DECLARE @Error INT IF DATEDIFF(dd, @OrderDate, GETDATE()) > 7 BEGIN SELECT @InsertedOrderDate = NULL PRINT 'Invalid Order Date' PRINT 'Supplied Order Date was greater than 7 days old.' PRINT 'The value has been reset to NULL' ELSE BEGIN SELECT @InsertedOrderDate = CONVERT(DATETIME, CONVERT(VARCHAR, @OrderDate, 112)) PRINT 'The time of Day in Order Date was truncated' END INSERT INTO Orders VALUES ( @CustomerID, @EmployeeID, @OrderDate, @RequiredDate, @ShippedDate, @ShipVia, @Freight, @ShipName, @ShipAddress, @ShipCity, @ShipRegion, @ShipPostalCode, @ShipCountry ) SELECT @Error = @@ERROR IF @Error != 0 BEGIN IF @Error = 547 BEGIN PRINT 'Supplied data violates data integrity rules' PRINT 'Check that the supplied customer number exists' PRINT 'in the system and try again' END ELSE BEGIN PRINT 'An unknown error occurred. Contact your System PRINT 'Administrator. The error was number ' + CONVERT(VARCHAR, @Error) END RETURN @Error END SELECT @OrderID = @@IDENTITY
調用:
USE Northwind GO DECLARE @MyIdent INT DECLARE @MyDate SMALLDATETIME DECLARE @Return INT SELECT @MyDate = GETDATE() EXEC @Return = spInsertDateValidatedOrder @CustomerID = 'ZXZXZ', @EmployeeID = 5, @OrderDate = @MyDate, @ShipVia = 3, @Freight = 5.00, @OrderID = @MyIdent OUTPUT IF @Return = 0 SELECT OrderID, CustomerID, EmployeeID, OrderDate, ShipName FROM Orders WHERE OrderID = @MyIdent ELSE PRINT 'Value Returned was ' + CONVERT(VARCHAR, @Return)
TRY/CATCH塊
SQL Server中的TRY/CATCH塊與C系列語言類似,語法如下:
BEGIN TRY {<sql statements>} END TRY BEGIN CATCH {<sql statements>} END CATCH
正如之前提到過,0~10的錯誤不會跳出TRY語句塊,11~19之間的錯誤會立即跳出TRY語句塊,進入CATCH語句塊中。
再一次改寫spInsertDateValidateOrder如下:
… … ELSE BEGIN SELECT @InsertedOrderDate = CONVERT(datetime,(CONVERT(varchar,@OrderDate,112))) PRINT 'The Time of Day in Order Date was truncated' END /* Establish our TRY/CATCH Block */ BEGIN TRY /* Create the new record */ INSERT INTO Orders VALUES ( @CustomerID, @EmployeeID, @InsertedOrderDate, @RequiredDate, @ShippedDate, @ShipVia, @Freight, @ShipName, @ShipAddress, @ShipCity, @ShipRegion, @ShipPostalCode, @ShipCountry ) END TRY BEGIN CATCH -- Uh oh, something went wrong, see if it's something -- we know what to do with DECLARE @ErrorNo int, @Severity tinyint, @State smallint, @LineNo int, @Message nvarchar(4000) SELECT @ErrorNo = ERROR_NUMBER(), @Severity = ERROR_SEVERITY(), @State = ERROR_STATE(), @LineNo = ERROR_LINE (), @Message = ERROR_MESSAGE() IF @ErrorNo = 547 -- The problem is a constraint violation. Print out some informational -- help to steer the user to the most likely problem. BEGIN PRINT 'Supplied data violates data integrity rules' PRINT 'Check that the supplied customer number exists' PRINT 'in the system and try again' END ELSE -- Oops, it's something we haven't anticipated, tell them that we -- don't know, print out the error. BEGIN PRINT 'An unknown error occurred. Contact your System Administrator' PRINT 'The error was number ' + CONVERT(varchar, @Error) END -- Regardless of the error, we're going to send it back to the calling -- piece of code so it can be handled at that level if necessary. RETURN @Error END CATCH /* Move the identity value from the newly inserted record into our output variable */ SELECT @OrderID = @@IDENTITY RETURN應用在TRY/CATCH中的幾個專用函數:
函數 |
返回類型 |
返回值 |
ERROR_NUMBER() |
INT |
在CATCH塊中調用時,返回導致運行CATCH塊的錯誤消息的錯誤號。 如果在CATCH塊作用域以外調用,則返回NULL。 |
ERROR_SEVERITY() |
INT |
在CATCH塊中調用時,返回導致CATCH塊運行的錯誤消息的嚴重級別。 如果在CATCH塊作用域以外調用,則返回NULL。 |
ERROR_STATE() |
INT |
當在CATCH塊中調用時,返回導致CATCH塊運行的錯誤消息的狀態號。 如果在CATCH塊作用域以外調用,則返回NULL。 |
ERROR_PROCEDUER() |
NVARCHAR(126) |
在CATCH塊中調用時,返回出現錯誤的存儲過程名稱。 如果該錯誤未在存儲過程或觸發器中出現,則返回NULL。 如果在CATCH塊作用域以外調用,則返回NULL。 |
ERROR_LINE() |
INT |
當在CATCH塊中調用時:
如果在CATCH塊作用域以外調用,則返回NULL。 |
ERROR_MESSAGE |
NVARCHAR(2048) |
在CATCH塊中調用時,返回導致CATCH塊運行的錯誤消息的完整文本。該文本包括為所有可替換參數提供的值,如長度、對象名或時間。 如果在CATCH塊作用域以外調用,則返回NULL。 |
12.7.2 在錯誤發生前處理錯誤
12.7.3 手動地激活錯誤
SQL Server允許客戶端創建一個運行時錯誤,客戶端可以使用它來調用錯誤處理程序並采取相應的動作。此處使用T-SQL中的RAISERROR命令來完成。它的語法如下:
RAISERROR (<message ID | message string>, <severity>, <state> [, <argument> [, <...n>]]) [WITH OPTION [, ...n]]
1. 消息ID/消息字符串
消息ID或消息字符串決定了向客戶端發送的消息。
使用消息ID會手動地激活指定的ID以及和該ID相關信息的錯誤,這個信息可以在master數據庫中的sysmessages表中找到。
提示:
如果想要了解SQL Server中預先確定的消息,那么可以執行SELECT * FROM master.sysMessages。這會包含使用sp_addmessage存儲過程或通過Enterprise Manager手動添加到系統的任何消息。
可以以特殊文本的形式提供消息字符串,而不是在sysmessages中創建更持久的消息:
RAISERROR ('Hi there, I''m an error', 1, 1)
會激活一個簡單的錯誤信息:
Msg 50000, Level 1, State 50000
Hi there, I'm an error
注意一下對齊的消息號是50000,即使我們並沒有提供它。這是對任何特別錯誤的默認錯誤值。可以使用WITH SETERROR選項來重寫它。
2. 嚴重性
嚴重性是基於該錯誤究竟有多差的指標。可是對於SQL Server,嚴重性代碼的意義有些古怪。它們基本上可以從報告性的(嚴重性1~18)、到系統級的(19~25)甚至是災難性的(20~25)。如果激活錯誤的嚴重性是19或更高的話(系統級的),那么也必須要指定WITH LOG選項。20以及更高級別會自動終止用戶的連接。
1~9 |
純報告性的,但是會在消息中返回一個特定的錯誤代碼。無論在RAISERROR中設置何種狀態,它總會以相同的錯誤號結束。 |
10 |
同樣是報告性的,但是不會在客戶端激活一個錯誤而且除了錯誤文本外,不會提供任何特定錯誤信息。 |
11~16 |
這些都會終止存儲過程的執行並在客戶端激活一個錯誤。根據這點,會根據任何設定的狀態值去顯示狀態。 |
17 |
通常只有SQL Server會使用這個嚴重性。它基本上只是SQL Server用完了資源而且不能完成請求。 |
18、19 |
這些都是非常嚴重的錯誤,而且暗示系統管理員要注意其中根本的原因。對於19來說,需要使用WITH LOG選項,並且如果使用NT或者Win 2K家族的操作系統,則該事件會寫入它們的事件日志中去。 |
20~25 |
本質上講,它們是致命的錯誤,會終止連接。 |
3. 狀態
狀態是個特殊的值。它會認出相同的錯誤會在代碼中多次出現。這個概念給予了發送位置標記的機會,這個位置正好是發生錯誤的地方。狀態值可以在1到127之間。
4. 錯誤參數
一些預先確定的錯誤會接受參數。這通過改變錯誤特有的性質來使錯誤在本質上更加的動態化。也可以格式化錯誤信息來接收參數。
動態信息通過使用占位符來完成。所有的占位符以%開始,而且根據傳遞的信息類別對它們進行編碼:
占位符類型指示符 |
類型值 |
D或i |
帶符號整數 |
O |
無符號八進制數 |
P |
指針 |
S |
字符串 |
U |
無符號整數 |
X或x |
無符號十六進制數 |
另外,還可以使用一些額外的標記和寬度信息來作為任何這些占位符的前綴:
標記 |
作用 |
- |
左調整——只在確定寬度的時候起作用 |
+ |
如果符號是帶符號的數據類型,則指示參數為正或為負 |
0 |
在數值類型值的左邊填充0直到達到了在寬度選項中指定的寬度 |
# |
只用於八進制和十六進制。根據是八進制還是十六進制來使用適當的前綴(0或0x) |
空格 |
如果數值為正,則在該值的左邊填入空格 |
寬度 |
設定想要為參數所保留的空間,指定"*"來自動地確定寬度 |
精度 |
設定輸出的數值型數據的最大位數 |
長/短 |
當參數類型為整數、八進制或十六進制時,使用h(short)或l(long)來設定。 |
5. WITH OPTION選項
現有三個選項可供使用:
-
LOG
-
SETERROR
-
NOWAIT
WITH LOG
這會告訴SQL Server在SQL Server錯誤日志和NT應用程序日志中記錄錯誤。這個選項需要的嚴重性級別要在19或19以上。
WITH SETERROR
默認地,RAISERROR命令不會給@@ERROR設置所產生的錯誤值——而@@ERROR反映了實際RAISERROR命令是成功或失敗的。SETERROR重寫了這個值並且把@@ERROR的值設為等於自己錯誤ID的值。
WITH NOWAIT
立即向客戶通知錯誤。
12.7.4 添加自定義的錯誤消息
可以使用系統存儲過程來向系統添加錯誤消息。這個存儲過程稱為sp_addmessage,語法如下:
sp_addmessage [@msgnum = ] <msg id>, [@severity =] <severity>, [@msgtest =] <'msg'> [, [@lang =] <'language'>] [, [@with_log =] [TRUE | FALSE]] [, [@replace =] 'replace']
除了@lang、@replace和@with_log有一點區別外,其他參數的含義與RAISERROR中是一樣的。
1. @lang
這個參數指定了消息應用的語言。這里可以指定消息為任何語言的版本,只要在syslanguages中支持這些語言。
2. @with_log
它和RAISERROR中的原理一樣。
3. @replace
如果是編輯現有的信息而非創建一個新消息的話,那么必須把@replace參數設置為"REPLACE"。如果不這樣做,那么如果消息已經存在,則會得到一個錯誤。
4. 使用sp_addmessage
示例——添加自定義的消息來告訴告訴用戶關於訂購日期的問題:
sp_addmessage @msgnum = 60000 @severity = 10 @msgtest = '%s is not a valid Order data. Order date must be within 7 days for current date.'
5. 刪除已有的自定義消息
要刪除自定義消息,請使用:
sp_dropmessage <msg num>