如何获取在存储过程中执行的存储过程的自定义错误?

How to get the custom error of a stored procedure which is executed inside a stored procedure?

我目前正在研究如何处理 SQL 服务器中嵌套存储过程的异常。

不幸的是,遇到一个场景,我想在存储过程[2]上输出自定义错误,该过程在存储过程[1]中执行。

这是一个例子:

/*
    create the master-table
*/
CREATE TABLE master_table
(
    pk_id INT IDENTITY(5,5),
    access_num INT UNIQUE NOT NULL,
    first_name NVARCHAR(MAX)
    PRIMARY KEY (pk_id)
)
GO

/*
    create the child-table
*/
CREATE TABLE child_table
(
    pk_id INT IDENTITY(1,1),
    val INT UNIQUE,
    msgs NVARCHAR(MAX) NOT NULL
    PRIMARY KEY (pk_id)
)
GO


/*
    create the master procedure
*/
CREATE or ALTER PROC uspInsertIntoMaster
    @AccessNum INT,
    @FirstName NVARCHAR(MAX),
    @Message NVARCHAR(MAX)
AS
BEGIN
    DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
    SET XACT_ABORT, NOCOUNT ON;

    BEGIN TRY
        BEGIN TRAN
            INSERT INTO master_table (access_num, first_name)
            VALUES (@AccessNum, @FirstName)

            EXEC uspInsertIntoChild 4, @Message;

        IF (@@TRANCOUNT = 1)
            COMMIT
    END TRY

    BEGIN CATCH
        IF @@TRANCOUNT > 0 ROLLBACK;

        THROW;

        IF (XACT_STATE()) = -1
                BEGIN
                    ROLLBACK TRANSACTION;
                END;

        IF (XACT_STATE()) = 1
            BEGIN
                COMMIT TRANSACTION;
            END;
    END CATCH
END
GO


/*
    create the child procedure
*/
CREATE or ALTER PROC uspInsertIntoChild
    @Value INT,
    @Message NVARCHAR(MAX)
AS
    DECLARE @ErrorMessage NVARCHAR(2048);
BEGIN
    DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
    SET XACT_ABORT, NOCOUNT ON;

    BEGIN TRY
        IF EXISTS(SELECT 1 FROM child_table WHERE val = @Value)
            BEGIN
                SET @ErrorMessage = FORMATMESSAGE(50010, @Value);
                THROW 50010, @ErrorMessage, 1;
            END;

        ELSE
            BEGIN TRAN 
                INSERT INTO child_table (val, msgs) VALUES (@Value, @Message)

            IF (@@TRANCOUNT > 0)
                COMMIT
    END TRY

    BEGIN CATCH
        IF @@TRANCOUNT > 0 ROLLBACK;

        THROW;

        IF (XACT_STATE()) = -1
                BEGIN
                    ROLLBACK TRANSACTION;
                END;

        IF (XACT_STATE()) = 1
            BEGIN
                COMMIT TRANSACTION;
            END;
    END CATCH
END
GO

自定义错误消息

EXEC sys.sp_addmessage 
    @msgnum = 50010, 
    @severity = 16, 
    @msgtext = N'Invalid Input. Error occurred at procedure uspInsertIntoChild',
    @lang = 'us_english';   
GO

如果我尝试执行下面的代码。我希望它在第二次执行时失败并抛出上面的自定义错误。

EXEC uspInsertIntoMaster 4, N'miKeL', N'Get Well Soon'
EXEC uspInsertIntoMaster 5, N'miKeL', N'Get Well Soon'

但不幸的是,我没有遇到自定义错误

Msg 50010, Level 16, State 1, Procedure uspInsertIntoChild, Line 14 [Batch Start Line 105] Error: 50010, Severity: 16, State: 1. (Params:). The error is printed in terse mode because there was error during formatting. Tracing, ETW, notifications etc are skipped.


编辑(@Charlieface)

首先,我先从master proc中删除了TRY/CATCH块,然后将两者都删除,但仍然出现相同的错误。我还按照@M.Ali的建议删除了DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS

Master Proc

CREATE or ALTER PROC uspInsertIntoMaster
    @AccessNum INT,
    @FirstName NVARCHAR(30),
    @Message NVARCHAR(MAX)
AS
BEGIN
    SET XACT_ABORT, NOCOUNT ON;
    
    BEGIN TRANSACTION
        INSERT INTO master_table (access_num, first_name)
        VALUES (@AccessNum, @FirstName)

        EXEC uspInsertIntoChild 4, @Message;

    IF (@@TRANCOUNT = 1)
        COMMIT TRANSACTION
END
GO

Child Proc

CREATE or ALTER PROC uspInsertIntoChild
    @Value INT,
    @Message NVARCHAR(MAX)
AS
    DECLARE @ErrorMessage NVARCHAR(MAX);
BEGIN
    
    SET XACT_ABORT, NOCOUNT ON;
    IF EXISTS(SELECT 1 FROM child_table WHERE val = @Value)
        BEGIN
            SET @ErrorMessage = FORMATMESSAGE(50010, @Value);
            -- I want to thow
            THROW 50010, @ErrorMessage, 1;
        END;

    ELSE
        BEGIN TRANSACTION
            INSERT INTO child_table (val, msgs) VALUES (@Value, @Message)

        IF (@@TRANCOUNT > 0)
            COMMIT TRANSACTION
END
GO

编辑 2

愚蠢的我。我编辑了格式化的消息。但从未想过还要编辑指示字符串字符的控制字符 %s。我试图将一个整数参数传递给错误消息,所以它应该是 %i.

最后的 FORMATTED 错误信息

EXEC sys.sp_addmessage 
    @msgnum = 50010, 
    @severity = 16, 
    @msgtext =
    N'Invalid Input %i. Error occured at procedure uspInsertIntoChild', /*Mobile number [%s] is invalid. Input a valid number*/
    @lang = 'us_english';   
GO

您使用的消息没有任何格式参数,因此 FORMATMESSAGE 抛出错误。

你需要这样的东西:

EXEC sys.sp_addmessage 
    @msgnum = 50010, 
    @severity = 16, 
    @msgtext = N'Invalid Input %s. Error occurred at procedure uspInsertIntoChild',
    @lang = 'us_english';   

顺便说一句,如果你使用XACT_ABORT ON那么就没有必要捕获和回滚。只有当你使用自定义日志记录时才有必要,这在这种情况你不是。

此外,您当前拥有的代码由于其他原因无法使用:

  • CATCH 中,您通常 ROLLBACK 永远不会 COMMIT
  • 如果XACT_STATE = -1那么ROLLBACK会失败
  • 反正在THROW;之后没有代码被执行。

所以我会说删除整个 TRY/CATCH,保留 XACT_ABORT ON 并像使用 sp_addmessage[=22 那样记录消息=]