如何获取在存储过程中执行的存储过程的自定义错误?
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 那样记录消息=]
我目前正在研究如何处理 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 那样记录消息=]