zoukankan      html  css  js  c++  java
  • SQL Server 2005 存储过程

    (《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>
  • 相关阅读:
    meta标签设置(移动端)
    清除浮动
    响应式设计
    堆和堆排序
    O(n^2)以及O(nlogn)时间复杂度的排序算法
    求数组的最大连续子数组和
    HTTP缓存原理
    将两个有序数组合并为一个有序数组
    如何实现居中对齐
    查找字符串中出现最多的字符
  • 原文地址:https://www.cnblogs.com/qwertWZ/p/3036829.html
Copyright © 2011-2022 走看看