如何在不结束 SQL 服务器存储过程执行的情况下在内部事务中抛出异常?

How to THROW exception in inner transaction without ending SQL Server stored procedure execution?

我的objective 是将异常抛回给调用者,但继续执行SQL Server 存储过程。因此,本质上,据我所知,尽管 SQL 服务器没有 try..catch..finally 块的概念,但我要完成的是 try..catch..finally block

我有一个示例 stored procedure 来说明。这只是我想出的一个例子,所以请不要过多关注 table 架构。希望您理解我在这里要执行的操作的要点。无论如何,存储过程包含一个 explicit transaction,它在 catch block 中抛出一个 exception。如果 THROW 被执行,则在 try..catch 块之后还有进一步的执行,但它永远不会执行。据我了解,至少在SQL服务器中,THROW无法区分内部和外部事务或嵌套事务。

在这个存储过程中,我有两个table:Tbl1Tbl2Tbl1Tbl1.ID 上有一个 primary keyTbl2EmpFK 上有一个 foreign key 映射到 Tbl1.IDEmpID 具有唯一约束。 Tbl1 中不能插入重复记录。 Tbl1Tbl2 都在 ID 上有主键,并使用身份增量进行自动插入。存储过程有三个输入参数,其中一个是employeeID.

在内部事务中,在 Tbl1 中插入了一条记录 -- 添加了一个新的员工 ID。如果它失败了,这个想法是事务应该优雅地出错,但存储过程应该仍然继续 运行 直到完成。不管table插入成功还是失败,后面都会使用EmpID填写EmpFk.

在 try..catch 块之后,我通过传递的 employeeID 参数执行 Tbl1.ID 的查找进入存储过程。然后,我向 TBl2 中插入一条记录; Tbl1.IDTbl2.EmpFK.

的值

(你可能会问 "why use such a schema? Why not combine into one table with such a small dataset?" 同样,这只是一个例子。它不一定是员工。你可以选择任何东西。它只是一个小部件。想象一下 Tbl1 可能包含非常非常大的数据集。确定的是有两个 table 具有主键/外键关系。)

这是示例数据集:

Tbl1
ID EmpID
1  AAA123
2  AAB123
3  AAC123

Tbl2
ID Role        Location EmpFK
1  Junior      NW       1
2  Senior      NW       2
3  Manager     NE       2
4  Sr Manager  SE       3
5  Director    SW       3

下面是示例存储过程:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[usp_TestProc]

    @employeeID VARCHAR(10)
    ,@role VARCHAR(50)
    ,@location VARCHAR(50)

AS
BEGIN

    SET NOCOUNT ON;

    DECLARE @employeeFK INT;

    BEGIN TRY
        BEGIN TRANSACTION MYTRAN;

            INSERT [Tbl1] (
                [EmpID]
            )
            VALUES (
                @employeeID
            );

        COMMIT TRANSACTION MYTRAN;
    END TRY

    BEGIN CATCH

        IF @@TRANCOUNT > 0
        BEGIN

            ROLLBACK TRANSACTION MYTRAN;

        END;

        THROW; -- Raises exception, exiting stored procedure

    END CATCH;

    SELECT
        @employeeFK = [ID]
    FROM
        [Tbl1]
    WHERE
        [EmpID] = @employeeID;

    INSERT [Tbl2] (
        [Role]
        ,[Location]
        ,[EmpFK]
    )
    VALUES (
        @role
        ,@location
        ,@employeeFK
    );

END;

所以,我还是想 return 将错误发送给调用者,即记录错误,但我不希望它停止执行存储过程。它应该继续非常类似于 try..catch..finally 块。这可以用 THROW 来完成,还是我必须使用其他方法?

也许我弄错了,但 THROW 不是 RAISERROR 的升级版本吗?今后,我们应该使用前者来处理异常吗?

我过去曾在这些情况下使用过 RAISERROR,它非常适合我。但 THROW 是一个更简单、更优雅的解决方案,imo,并且可能是未来更好的实践。我不太确定。

提前感谢您的帮助。

What's set in stone is there are two tables which have a primary key / foreign key relationship.

在内部事务中使用 THROW 不是做你想做的事情的方法。从您的代码来看,您想要插入一个新员工,除非该员工已经存在,然后,无论该员工是否已经存在,您都希望在第二次插入子项时使用该员工的 PK/id table.

一种方法是拆分逻辑。这是我的意思的伪代码:

IF NOT EXISTS(Select employee with @employeeId)
  INSERT the new employee

SELECT @employeeFK like you are doing.

INSERT into Table2 like you are doing.

如果传了一个已经存在的@employeeId,你还需要报错,你可以在IF后面放一个ELSE,填充一个字符串变量,在proc的最后,如果变量是填充,然后 throw/raise 一个错误。